Merge "API Feedback - remove NavigationSuiteScaffoldScope and alignment modifier. Plus make NavigationSuiteScaffoldLayout public." into androidx-main
diff --git a/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml b/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml
index ac48471..7155089 100644
--- a/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml
+++ b/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.0.0-alpha05" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-alpha05)" variant="all" version="8.0.0-alpha05">
+<issues format="6" by="lint 8.2.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-beta01)" variant="all" version="8.2.0-beta01">
 
     <issue
         id="ExperimentalAnnotationRetention"
@@ -327,6 +327,15 @@
 
     <issue
         id="UnsafeOptInUsageError"
+        message="This declaration is opt-in and its usage should be marked with `@sample.optin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class)`"
+        errorLine1="        player.accessor"
+        errorLine2="               ~~~~~~~~">
+        <location
+            file="src/main/java/sample/optin/RegressionTestKotlin298322402.kt"/>
+    </issue>
+
+    <issue
+        id="UnsafeOptInUsageError"
         message="This declaration is opt-in and its usage should be marked with `@sample.kotlin.ExperimentalJavaAnnotation` or `@OptIn(markerClass = sample.kotlin.ExperimentalJavaAnnotation.class)`"
         errorLine1="        AnnotatedJavaClass experimentalObject = new AnnotatedJavaClass();"
         errorLine2="                                                ~~~~~~~~~~~~~~~~~~~~~~~~">
diff --git a/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/AnnotatedJavaMembers.java b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/AnnotatedJavaMembers.java
index 82955e3..88faa0f 100644
--- a/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/AnnotatedJavaMembers.java
+++ b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/AnnotatedJavaMembers.java
@@ -38,6 +38,11 @@
         return -1;
     }
 
+    @ExperimentalJavaAnnotation
+    public int getAccessor() {
+        return -1;
+    }
+
     public int getFieldWithSetMarker() {
         return mFieldWithSetMarker;
     }
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/RegressionTestKotlin298322402.kt
similarity index 66%
rename from camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
rename to annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/RegressionTestKotlin298322402.kt
index 7a355f8..5a8eb40 100644
--- a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
+++ b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/optin/RegressionTestKotlin298322402.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,16 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+@file:Suppress("unused")
 
-package androidx.camera.effects;
+package sample.optin
 
-import androidx.annotation.RestrictTo;
-
-/**
- * Provides a portrait post-processing effect.
- *
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class Portrait {
-    // TODO: implement this
+internal class RegressionTestKotlin298322402 {
+    fun testMethod(player: AnnotatedJavaMembers) {
+        player.accessor
+    }
 }
diff --git a/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt b/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt
index ef20bed..54cb668 100644
--- a/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt
+++ b/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalDetector.kt
@@ -35,12 +35,14 @@
 import com.intellij.psi.PsiAnnotation
 import com.intellij.psi.PsiClass
 import com.intellij.psi.PsiClassType
+import com.intellij.psi.PsiComment
 import com.intellij.psi.PsiElement
 import com.intellij.psi.PsiField
 import com.intellij.psi.PsiMethod
 import com.intellij.psi.PsiModifier
 import com.intellij.psi.PsiModifierListOwner
 import com.intellij.psi.PsiPackage
+import com.intellij.psi.PsiWhiteSpace
 import com.intellij.psi.impl.source.PsiClassReferenceType
 import com.intellij.psi.search.GlobalSearchScope
 import com.intellij.psi.util.PsiTreeUtil
@@ -643,24 +645,38 @@
             else -> throw IllegalArgumentException("Unsupported element type")
         }
 
-        // If the element has a modifier list, e.g. not an anonymous class or lambda, then place the
-        // insertion point at the beginning of the modifiers list. This ensures that we don't insert
-        // the annotation in an invalid position, such as after the "public" or "fun" keywords. We
-        // also don't want to place it on the element range itself, since that would place it before
-        // the comments.
-        val elementLocation = context.getLocation(element.modifierList ?: element as UElement)
+        // If the element can include modifiers, e.g. not an anonymous class or lambda, find the
+        // where the list should start. This ensures that we don't insert the annotation in an
+        // invalid position, such as after the `public` or `fun` keywords. We also don't want to
+        // place it on the element range itself, since that would place it before the comments.
+        val elementSourcePsi = element.sourcePsi
+        val elementForInsert = if (elementSourcePsi is PsiModifierListOwner) {
+            elementSourcePsi.asIterable().firstOrNull { child ->
+                child !is PsiWhiteSpace && child !is PsiComment
+            } ?: throw IllegalArgumentException("Failed to locate element declaration")
+        } else {
+            element
+        }
 
         return fix()
-            .replace()
             .name("Add '$annotation' annotation to $elementLabel")
-            .range(elementLocation)
-            .beginning()
-            .shortenNames()
-            .with("$annotation ")
+            .annotate(annotation, true)
+            .range(context.getLocation(elementForInsert))
             .build()
     }
 
     /**
+     * Returns an iterable of child elements.
+     */
+    private fun PsiElement.asIterable(): Iterable<PsiElement> = object : Iterable<PsiElement> {
+        override fun iterator(): Iterator<PsiElement> = object : Iterator<PsiElement> {
+            private var current = firstChild
+            override fun hasNext(): Boolean = current != null
+            override fun next(): PsiElement = current.apply { current = nextSibling }
+        }
+    }
+
+    /**
      * Reports an issue and trims indentation on the [message].
      */
     private fun report(
diff --git a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ExperimentalDetectorTest.kt b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ExperimentalDetectorTest.kt
index 25567ce..21e170e 100644
--- a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ExperimentalDetectorTest.kt
+++ b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ExperimentalDetectorTest.kt
@@ -72,52 +72,40 @@
         val expectedFix = """
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 25: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTime.class)' annotation to 'getDateUnsafe':
 @@ -24 +24
--     int getDateUnsafe() {
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class) int getDateUnsafe() {
++     @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class)
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 25: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
 @@ -24 +24
--     int getDateUnsafe() {
-+     @ExperimentalDateTime int getDateUnsafe() {
++     @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 25: Add '@sample.experimental.ExperimentalDateTime' annotation to containing class 'UseJavaExperimentalFromJava':
 @@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
-+ @ExperimentalDateTime @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
++ @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 26: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTime.class)' annotation to 'getDateUnsafe':
 @@ -24 +24
--     int getDateUnsafe() {
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class) int getDateUnsafe() {
++     @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class)
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 26: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
 @@ -24 +24
--     int getDateUnsafe() {
-+     @ExperimentalDateTime int getDateUnsafe() {
++     @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 26: Add '@sample.experimental.ExperimentalDateTime' annotation to containing class 'UseJavaExperimentalFromJava':
 @@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
-+ @ExperimentalDateTime @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
++ @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 53: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocation.class)' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -50 +50
--     @ExperimentalDateTime
-+     @androidx.annotation.OptIn(markerClass = ExperimentalLocation.class) @ExperimentalDateTime
++     @androidx.annotation.OptIn(markerClass = ExperimentalLocation.class)
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 53: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -50 +50
--     @ExperimentalDateTime
-+     @ExperimentalLocation @ExperimentalDateTime
++     @ExperimentalLocation
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 53: Add '@sample.experimental.ExperimentalLocation' annotation to containing class 'UseJavaExperimentalFromJava':
 @@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
-+ @ExperimentalLocation @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
++ @ExperimentalLocation
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 54: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocation.class)' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -50 +50
--     @ExperimentalDateTime
-+     @androidx.annotation.OptIn(markerClass = ExperimentalLocation.class) @ExperimentalDateTime
++     @androidx.annotation.OptIn(markerClass = ExperimentalLocation.class)
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 54: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -50 +50
--     @ExperimentalDateTime
-+     @ExperimentalLocation @ExperimentalDateTime
++     @ExperimentalLocation
 Fix for src/sample/experimental/UseJavaExperimentalFromJava.java line 54: Add '@sample.experimental.ExperimentalLocation' annotation to containing class 'UseJavaExperimentalFromJava':
 @@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
-+ @ExperimentalLocation @SuppressWarnings({"unused", "WeakerAccess", "deprecation"})
++ @ExperimentalLocation
         """.trimIndent()
         /* ktlint-enable max-line-length */
 
@@ -189,53 +177,41 @@
 
         val expectedFix = """
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 29: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalDateTime::class)' annotation to 'getDateUnsafe':
-@@ -1 +1
-- /*
-+ @androidx.annotation.OptIn(ExperimentalDateTime::class) /*
+@@ -25 +25
++     @androidx.annotation.OptIn(ExperimentalDateTime::class)
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 29: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
-@@ -1 +1
-- /*
-+ @ExperimentalDateTime /*
+@@ -25 +25
++     @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 29: Add '@sample.experimental.ExperimentalDateTime' annotation to containing class 'UseJavaExperimentalFromKt':
-@@ -23 +23
-- @Suppress("unused", "MemberVisibilityCanBePrivate")
-+ @ExperimentalDateTime @Suppress("unused", "MemberVisibilityCanBePrivate")
+@@ -1 +1
++ @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 30: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalDateTime::class)' annotation to 'getDateUnsafe':
-@@ -1 +1
-- /*
-+ @androidx.annotation.OptIn(ExperimentalDateTime::class) /*
+@@ -25 +25
++     @androidx.annotation.OptIn(ExperimentalDateTime::class)
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 30: Add '@sample.experimental.ExperimentalDateTime' annotation to 'getDateUnsafe':
-@@ -1 +1
-- /*
-+ @ExperimentalDateTime /*
+@@ -25 +25
++     @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 30: Add '@sample.experimental.ExperimentalDateTime' annotation to containing class 'UseJavaExperimentalFromKt':
-@@ -23 +23
-- @Suppress("unused", "MemberVisibilityCanBePrivate")
-+ @ExperimentalDateTime @Suppress("unused", "MemberVisibilityCanBePrivate")
+@@ -1 +1
++ @ExperimentalDateTime
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 57: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalLocation::class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -54 +54
--     @ExperimentalDateTime
-+     @androidx.annotation.OptIn(ExperimentalLocation::class) @ExperimentalDateTime
+@@ -51 +51
++     @androidx.annotation.OptIn(ExperimentalLocation::class)
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 57: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -54 +54
--     @ExperimentalDateTime
-+     @ExperimentalLocation @ExperimentalDateTime
+@@ -51 +51
++     @ExperimentalLocation
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 57: Add '@sample.experimental.ExperimentalLocation' annotation to containing class 'UseJavaExperimentalFromKt':
-@@ -23 +23
-- @Suppress("unused", "MemberVisibilityCanBePrivate")
-+ @ExperimentalLocation @Suppress("unused", "MemberVisibilityCanBePrivate")
+@@ -1 +1
++ @ExperimentalLocation
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 58: Add '@androidx.annotation.OptIn(sample.experimental.ExperimentalLocation::class)' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -54 +54
--     @ExperimentalDateTime
-+     @androidx.annotation.OptIn(ExperimentalLocation::class) @ExperimentalDateTime
+@@ -51 +51
++     @androidx.annotation.OptIn(ExperimentalLocation::class)
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 58: Add '@sample.experimental.ExperimentalLocation' annotation to 'getDateExperimentalLocationUnsafe':
-@@ -54 +54
--     @ExperimentalDateTime
-+     @ExperimentalLocation @ExperimentalDateTime
+@@ -51 +51
++     @ExperimentalLocation
 Fix for src/sample/experimental/UseJavaExperimentalFromKt.kt line 58: Add '@sample.experimental.ExperimentalLocation' annotation to containing class 'UseJavaExperimentalFromKt':
-@@ -23 +23
-- @Suppress("unused", "MemberVisibilityCanBePrivate")
-+ @ExperimentalLocation @Suppress("unused", "MemberVisibilityCanBePrivate")
+@@ -1 +1
++ @ExperimentalLocation
         """.trimIndent()
         /* ktlint-enable max-line-length */
 
@@ -286,100 +262,76 @@
         val expectedFix = """
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 25: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'getDateUnsafe':
 @@ -24 +24
--     int getDateUnsafe() {
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) int getDateUnsafe() {
++     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 25: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'getDateUnsafe':
 @@ -24 +24
--     int getDateUnsafe() {
-+     @ExperimentalDateTimeKt int getDateUnsafe() {
++     @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 25: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to containing class 'UseKtExperimentalFromJava':
 @@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess"})
-+ @ExperimentalDateTimeKt @SuppressWarnings({"unused", "WeakerAccess"})
++ @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 26: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'getDateUnsafe':
 @@ -24 +24
--     int getDateUnsafe() {
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) int getDateUnsafe() {
++     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 26: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'getDateUnsafe':
 @@ -24 +24
--     int getDateUnsafe() {
-+     @ExperimentalDateTimeKt int getDateUnsafe() {
++     @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 26: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to containing class 'UseKtExperimentalFromJava':
 @@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess"})
-+ @ExperimentalDateTimeKt @SuppressWarnings({"unused", "WeakerAccess"})
++ @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 54: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocationKt.class)' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -51 +51
--     @ExperimentalDateTimeKt
-+     @androidx.annotation.OptIn(markerClass = ExperimentalLocationKt.class) @ExperimentalDateTimeKt
++     @androidx.annotation.OptIn(markerClass = ExperimentalLocationKt.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 54: Add '@sample.experimental.ExperimentalLocationKt' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -51 +51
--     @ExperimentalDateTimeKt
-+     @ExperimentalLocationKt @ExperimentalDateTimeKt
++     @ExperimentalLocationKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 54: Add '@sample.experimental.ExperimentalLocationKt' annotation to containing class 'UseKtExperimentalFromJava':
 @@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess"})
-+ @ExperimentalLocationKt @SuppressWarnings({"unused", "WeakerAccess"})
++ @ExperimentalLocationKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 55: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalLocationKt.class)' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -51 +51
--     @ExperimentalDateTimeKt
-+     @androidx.annotation.OptIn(markerClass = ExperimentalLocationKt.class) @ExperimentalDateTimeKt
++     @androidx.annotation.OptIn(markerClass = ExperimentalLocationKt.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 55: Add '@sample.experimental.ExperimentalLocationKt' annotation to 'getDateExperimentalLocationUnsafe':
 @@ -51 +51
--     @ExperimentalDateTimeKt
-+     @ExperimentalLocationKt @ExperimentalDateTimeKt
++     @ExperimentalLocationKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 55: Add '@sample.experimental.ExperimentalLocationKt' annotation to containing class 'UseKtExperimentalFromJava':
 @@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess"})
-+ @ExperimentalLocationKt @SuppressWarnings({"unused", "WeakerAccess"})
++ @ExperimentalLocationKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 88: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'regressionTestStaticUsage':
 @@ -87 +87
--     void regressionTestStaticUsage() {
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) void regressionTestStaticUsage() {
++     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 88: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'regressionTestStaticUsage':
 @@ -87 +87
--     void regressionTestStaticUsage() {
-+     @ExperimentalDateTimeKt void regressionTestStaticUsage() {
++     @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 88: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to containing class 'UseKtExperimentalFromJava':
 @@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess"})
-+ @ExperimentalDateTimeKt @SuppressWarnings({"unused", "WeakerAccess"})
++ @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 89: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'regressionTestStaticUsage':
 @@ -87 +87
--     void regressionTestStaticUsage() {
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) void regressionTestStaticUsage() {
++     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 89: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'regressionTestStaticUsage':
 @@ -87 +87
--     void regressionTestStaticUsage() {
-+     @ExperimentalDateTimeKt void regressionTestStaticUsage() {
++     @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 89: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to containing class 'UseKtExperimentalFromJava':
 @@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess"})
-+ @ExperimentalDateTimeKt @SuppressWarnings({"unused", "WeakerAccess"})
++ @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 96: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTimeKt.class)' annotation to 'regressionTestInlineUsage':
 @@ -95 +95
--     void regressionTestInlineUsage() {
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class) void regressionTestInlineUsage() {
++     @androidx.annotation.OptIn(markerClass = ExperimentalDateTimeKt.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 96: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to 'regressionTestInlineUsage':
 @@ -95 +95
--     void regressionTestInlineUsage() {
-+     @ExperimentalDateTimeKt void regressionTestInlineUsage() {
++     @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 96: Add '@sample.experimental.ExperimentalDateTimeKt' annotation to containing class 'UseKtExperimentalFromJava':
 @@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess"})
-+ @ExperimentalDateTimeKt @SuppressWarnings({"unused", "WeakerAccess"})
++ @ExperimentalDateTimeKt
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 97: Add '@androidx.annotation.OptIn(markerClass = sample.experimental.ExperimentalDateTime.class)' annotation to 'regressionTestInlineUsage':
 @@ -95 +95
--     void regressionTestInlineUsage() {
-+     @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class) void regressionTestInlineUsage() {
++     @androidx.annotation.OptIn(markerClass = ExperimentalDateTime.class)
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 97: Add '@sample.experimental.ExperimentalDateTime' annotation to 'regressionTestInlineUsage':
 @@ -95 +95
--     void regressionTestInlineUsage() {
-+     @ExperimentalDateTime void regressionTestInlineUsage() {
++     @ExperimentalDateTime
 Fix for src/sample/experimental/UseKtExperimentalFromJava.java line 97: Add '@sample.experimental.ExperimentalDateTime' annotation to containing class 'UseKtExperimentalFromJava':
 @@ -19 +19
-- @SuppressWarnings({"unused", "WeakerAccess"})
-+ @ExperimentalDateTime @SuppressWarnings({"unused", "WeakerAccess"})
++ @ExperimentalDateTime
         """.trimIndent()
         /* ktlint-enable max-line-length */
 
diff --git a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
index 99ca8e3..f26e382 100644
--- a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
+++ b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/RequiresOptInDetectorTest.kt
@@ -23,7 +23,6 @@
 import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
 import com.android.tools.lint.checks.infrastructure.TestLintResult
 import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
-import com.android.tools.lint.checks.infrastructure.TestMode
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -39,7 +38,6 @@
                 *testFiles
             )
             .issues(*ExperimentalDetector.ISSUES.toTypedArray())
-            .testModes(TestMode.PARTIAL)
             .run()
     }
 
@@ -444,6 +442,38 @@
         check(*input).expect(expected)
     }
 
+    @Test
+    fun regressionTestKotlin298322402() {
+        val input = arrayOf(
+            javaSample("sample.optin.ExperimentalJavaAnnotation"),
+            javaSample("sample.optin.AnnotatedJavaMembers"),
+            ktSample("sample.optin.RegressionTestKotlin298322402")
+        )
+
+        /* ktlint-disable max-line-length */
+        val expected = """
+src/sample/optin/RegressionTestKotlin298322402.kt:22: Error: This declaration is opt-in and its usage should be marked with @sample.optin.ExperimentalJavaAnnotation or @OptIn(markerClass = sample.optin.ExperimentalJavaAnnotation.class) [UnsafeOptInUsageError]
+        player.accessor
+               ~~~~~~~~
+1 errors, 0 warnings
+        """.trimIndent()
+
+        val expectedFix = """
+Fix for src/sample/optin/RegressionTestKotlin298322402.kt line 22: Add '@androidx.annotation.OptIn(sample.optin.ExperimentalJavaAnnotation::class)' annotation to 'testMethod':
+@@ -21 +21
++     @androidx.annotation.OptIn(ExperimentalJavaAnnotation::class)
+Fix for src/sample/optin/RegressionTestKotlin298322402.kt line 22: Add '@sample.optin.ExperimentalJavaAnnotation' annotation to 'testMethod':
+@@ -21 +21
++     @ExperimentalJavaAnnotation
+Fix for src/sample/optin/RegressionTestKotlin298322402.kt line 22: Add '@sample.optin.ExperimentalJavaAnnotation' annotation to containing class 'RegressionTestKotlin298322402':
+@@ -1 +1
++ @ExperimentalJavaAnnotation
+        """.trimIndent()
+        /* ktlint-enable max-line-length */
+
+        check(*input).expectFixDiffs(expectedFix).expect(expected)
+    }
+
     /* ktlint-disable max-line-length */
     companion object {
         /**
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
index c46429c..c2eac42 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
@@ -112,8 +112,10 @@
         mAppSearchDir = mTemporaryFolder.newFolder();
         mAppSearchImpl = AppSearchImpl.create(
                 mAppSearchDir,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
@@ -498,7 +500,8 @@
         InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
-                mAppSearchDir, new UnlimitedLimitConfig(), new DefaultIcingOptionsConfig(),
+                mAppSearchDir, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()),
                 initStatsBuilder, ALWAYS_OPTIMIZE, /*visibilityChecker=*/null);
 
         // Check recovery state
@@ -728,8 +731,10 @@
                 (callerAccess, packageName, prefixedSchema, visibilityStore) -> true;
         mAppSearchImpl = AppSearchImpl.create(
                 tempFolder,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 mockVisibilityChecker);
@@ -902,8 +907,10 @@
                 (callerAccess, packageName, prefixedSchema, visibilityStore) -> true;
         mAppSearchImpl = AppSearchImpl.create(
                 tempFolder,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 mockVisibilityChecker);
@@ -2861,8 +2868,10 @@
         // That document should be visible even from another instance.
         AppSearchImpl appSearchImpl2 = AppSearchImpl.create(
                 mAppSearchDir,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
@@ -2931,8 +2940,10 @@
         // Only the second document should be retrievable from another instance.
         AppSearchImpl appSearchImpl2 = AppSearchImpl.create(
                 mAppSearchDir,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
@@ -3008,8 +3019,10 @@
         // Only the second document should be retrievable from another instance.
         AppSearchImpl appSearchImpl2 = AppSearchImpl.create(
                 mAppSearchDir,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
@@ -3120,8 +3133,7 @@
         // Create a new mAppSearchImpl with a lower limit
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
-                mTemporaryFolder.newFolder(),
-                new LimitConfig() {
+                mTemporaryFolder.newFolder(), new AppSearchConfigImpl(new LimitConfig() {
                     @Override
                     public int getMaxDocumentSizeBytes() {
                         return 80;
@@ -3136,8 +3148,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                },
-                new DefaultIcingOptionsConfig(),
+                }, new DefaultIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3201,8 +3212,7 @@
         mAppSearchImpl.close();
         File tempFolder = mTemporaryFolder.newFolder();
         mAppSearchImpl = AppSearchImpl.create(
-                tempFolder,
-                new LimitConfig() {
+                tempFolder, new AppSearchConfigImpl(new LimitConfig() {
                     @Override
                     public int getMaxDocumentSizeBytes() {
                         return 80;
@@ -3217,8 +3227,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                },
-                new DefaultIcingOptionsConfig(),
+                }, new DefaultIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3260,8 +3269,7 @@
         // Close and reinitialize AppSearchImpl
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
-                tempFolder,
-                new LimitConfig() {
+                tempFolder, new AppSearchConfigImpl(new LimitConfig() {
                     @Override
                     public int getMaxDocumentSizeBytes() {
                         return 80;
@@ -3276,8 +3284,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                },
-                new DefaultIcingOptionsConfig(),
+                }, new DefaultIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3299,8 +3306,7 @@
         // Create a new mAppSearchImpl with a lower limit
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
-                mTemporaryFolder.newFolder(),
-                new LimitConfig() {
+                mTemporaryFolder.newFolder(), new AppSearchConfigImpl(new LimitConfig() {
                     @Override
                     public int getMaxDocumentSizeBytes() {
                         return Integer.MAX_VALUE;
@@ -3315,8 +3321,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                },
-                new DefaultIcingOptionsConfig(),
+                }, new DefaultIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3414,8 +3419,7 @@
         mAppSearchImpl.close();
         File tempFolder = mTemporaryFolder.newFolder();
         mAppSearchImpl = AppSearchImpl.create(
-                tempFolder,
-                new LimitConfig() {
+                tempFolder, new AppSearchConfigImpl(new LimitConfig() {
                     @Override
                     public int getMaxDocumentSizeBytes() {
                         return Integer.MAX_VALUE;
@@ -3430,8 +3434,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                },
-                new DefaultIcingOptionsConfig(),
+                }, new DefaultIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3512,8 +3515,7 @@
         // Reinitialize to make sure packages are parsed correctly on init
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
-                tempFolder,
-                new LimitConfig() {
+                tempFolder, new AppSearchConfigImpl(new LimitConfig() {
                     @Override
                     public int getMaxDocumentSizeBytes() {
                         return Integer.MAX_VALUE;
@@ -3528,8 +3530,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                },
-                new DefaultIcingOptionsConfig(),
+                }, new DefaultIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3571,8 +3572,7 @@
         // Create a new mAppSearchImpl with a lower limit
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
-                mTemporaryFolder.newFolder(),
-                new LimitConfig() {
+                mTemporaryFolder.newFolder(), new AppSearchConfigImpl(new LimitConfig() {
                     @Override
                     public int getMaxDocumentSizeBytes() {
                         return Integer.MAX_VALUE;
@@ -3587,8 +3587,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                },
-                new DefaultIcingOptionsConfig(),
+                }, new DefaultIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3726,8 +3725,7 @@
         // Create a new mAppSearchImpl with a lower limit
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
-                mTemporaryFolder.newFolder(),
-                new LimitConfig() {
+                mTemporaryFolder.newFolder(), new AppSearchConfigImpl(new LimitConfig() {
                     @Override
                     public int getMaxDocumentSizeBytes() {
                         return Integer.MAX_VALUE;
@@ -3742,8 +3740,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                },
-                new DefaultIcingOptionsConfig(),
+                }, new DefaultIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3811,8 +3808,7 @@
         mAppSearchImpl.close();
         File tempFolder = mTemporaryFolder.newFolder();
         mAppSearchImpl = AppSearchImpl.create(
-                tempFolder,
-                new LimitConfig() {
+                tempFolder, new AppSearchConfigImpl(new LimitConfig() {
                     @Override
                     public int getMaxDocumentSizeBytes() {
                         return Integer.MAX_VALUE;
@@ -3827,8 +3823,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                },
-                new DefaultIcingOptionsConfig(),
+                }, new DefaultIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3870,8 +3865,7 @@
         // Reinitialize to make sure replacements are correctly accounted for by init
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
-                tempFolder,
-                new LimitConfig() {
+                tempFolder, new AppSearchConfigImpl(new LimitConfig() {
                     @Override
                     public int getMaxDocumentSizeBytes() {
                         return Integer.MAX_VALUE;
@@ -3886,8 +3880,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                },
-                new DefaultIcingOptionsConfig(),
+                }, new DefaultIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3918,8 +3911,7 @@
         mAppSearchImpl.close();
         File tempFolder = mTemporaryFolder.newFolder();
         mAppSearchImpl = AppSearchImpl.create(
-                tempFolder,
-                new LimitConfig() {
+                tempFolder, new AppSearchConfigImpl(new LimitConfig() {
                     @Override
                     public int getMaxDocumentSizeBytes() {
                         return Integer.MAX_VALUE;
@@ -3934,8 +3926,7 @@
                     public int getMaxSuggestionCount() {
                         return 2;
                     }
-                },
-                new DefaultIcingOptionsConfig(),
+                }, new DefaultIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -4036,8 +4027,10 @@
                 (callerAccess, packageName, prefixedSchema, visibilityStore) -> false;
         mAppSearchImpl = AppSearchImpl.create(
                 tempFolder,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 mockVisibilityChecker);
@@ -4087,8 +4080,10 @@
                 (callerAccess, packageName, prefixedSchema, visibilityStore) -> true;
         mAppSearchImpl = AppSearchImpl.create(
                 tempFolder,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 mockVisibilityChecker);
@@ -4136,8 +4131,10 @@
                 (callerAccess, packageName, prefixedSchema, visibilityStore) -> true;
         mAppSearchImpl = AppSearchImpl.create(
                 tempFolder,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 mockVisibilityChecker);
@@ -4187,8 +4184,10 @@
                         callerAccess.getCallingPackageName().equals("visiblePackage");
         mAppSearchImpl = AppSearchImpl.create(
                 tempFolder,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 mockVisibilityChecker);
@@ -4530,8 +4529,10 @@
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
                 mAppSearchDir,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
@@ -4569,8 +4570,10 @@
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
                 mAppSearchDir,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
@@ -4600,8 +4603,10 @@
                 (callerAccess, packageName, prefixedSchema, visibilityStore) -> true;
         mAppSearchImpl = AppSearchImpl.create(
                 tempFolder,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 mockVisibilityChecker);
@@ -4696,8 +4701,10 @@
                         -> prefixedSchema.endsWith("VisibleType");
         mAppSearchImpl = AppSearchImpl.create(
                 tempFolder,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 mockVisibilityChecker);
@@ -4783,8 +4790,10 @@
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
                 mAppSearchDir,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/null,
                 ALWAYS_OPTIMIZE,
                 rejectChecker);
@@ -4885,8 +4894,10 @@
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
                 mAppSearchDir,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/null,
                 ALWAYS_OPTIMIZE,
                 visibilityChecker);
@@ -4945,8 +4956,10 @@
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
                 mAppSearchDir,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/null,
                 ALWAYS_OPTIMIZE,
                 rejectChecker);
@@ -5271,8 +5284,10 @@
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
                 mAppSearchDir,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/null,
                 ALWAYS_OPTIMIZE,
                 visibilityChecker);
@@ -5427,8 +5442,10 @@
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
                 mAppSearchDir,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/null,
                 ALWAYS_OPTIMIZE,
                 visibilityChecker);
@@ -5514,8 +5531,10 @@
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
                 mAppSearchDir,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/null,
                 ALWAYS_OPTIMIZE,
                 visibilityChecker);
@@ -5605,8 +5624,10 @@
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
                 mAppSearchDir,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/null,
                 ALWAYS_OPTIMIZE,
                 visibilityChecker);
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchLoggerTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchLoggerTest.java
index c9828f5..22afe67 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchLoggerTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchLoggerTest.java
@@ -75,8 +75,10 @@
     public void setUp() throws Exception {
         mAppSearchImpl = AppSearchImpl.create(
                 mTemporaryFolder.newFolder(),
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
@@ -151,6 +153,10 @@
         final int nativeDocumentSize = 7;
         final int nativeNumTokensIndexed = 8;
         final boolean nativeExceededMaxNumTokens = true;
+        final int nativeTermIndexLatencyMillis = 9;
+        final int nativeIntegerIndexLatencyMillis = 10;
+        final int nativeQualifiedIdJoinIndexLatencyMillis = 11;
+        final int nativeLiteIndexSortLatencyMillis = 12;
         PutDocumentStatsProto nativePutDocumentStats = PutDocumentStatsProto.newBuilder()
                 .setLatencyMs(nativeLatencyMillis)
                 .setDocumentStoreLatencyMs(nativeDocumentStoreLatencyMillis)
@@ -160,6 +166,10 @@
                 .setTokenizationStats(PutDocumentStatsProto.TokenizationStats.newBuilder()
                         .setNumTokensIndexed(nativeNumTokensIndexed)
                         .build())
+                .setTermIndexLatencyMs(nativeTermIndexLatencyMillis)
+                .setIntegerIndexLatencyMs(nativeIntegerIndexLatencyMillis)
+                .setQualifiedIdJoinIndexLatencyMs(nativeQualifiedIdJoinIndexLatencyMillis)
+                .setLiteIndexSortLatencyMs(nativeLiteIndexSortLatencyMillis)
                 .build();
         PutDocumentStats.Builder pBuilder = new PutDocumentStats.Builder(PACKAGE_NAME, DATABASE);
 
@@ -174,6 +184,14 @@
                 nativeIndexMergeLatencyMillis);
         assertThat(pStats.getNativeDocumentSizeBytes()).isEqualTo(nativeDocumentSize);
         assertThat(pStats.getNativeNumTokensIndexed()).isEqualTo(nativeNumTokensIndexed);
+        assertThat(pStats.getNativeTermIndexLatencyMillis()).isEqualTo(
+                nativeTermIndexLatencyMillis);
+        assertThat(pStats.getNativeIntegerIndexLatencyMillis()).isEqualTo(
+                nativeIntegerIndexLatencyMillis);
+        assertThat(pStats.getNativeQualifiedIdJoinIndexLatencyMillis()).isEqualTo(
+                nativeQualifiedIdJoinIndexLatencyMillis);
+        assertThat(pStats.getNativeLiteIndexSortLatencyMillis()).isEqualTo(
+                nativeLiteIndexSortLatencyMillis);
     }
 
     @Test
@@ -350,8 +368,10 @@
         InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
         AppSearchImpl appSearchImpl = AppSearchImpl.create(
                 mTemporaryFolder.newFolder(),
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 initStatsBuilder,
                 ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
@@ -381,8 +401,10 @@
 
         AppSearchImpl appSearchImpl = AppSearchImpl.create(
                 folder,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
@@ -419,7 +441,8 @@
         // Create another appsearchImpl on the same folder
         InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
         appSearchImpl = AppSearchImpl.create(
-                folder, new UnlimitedLimitConfig(), new DefaultIcingOptionsConfig(),
+                folder, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()),
                 initStatsBuilder, ALWAYS_OPTIMIZE, /*visibilityChecker=*/null);
         InitializeStats iStats = initStatsBuilder.build();
 
@@ -446,7 +469,8 @@
         final File folder = mTemporaryFolder.newFolder();
 
         AppSearchImpl appSearchImpl = AppSearchImpl.create(
-                folder, new UnlimitedLimitConfig(), new DefaultIcingOptionsConfig(),
+                folder, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE, /*visibilityChecker=*/null);
 
         List<AppSearchSchema> schemas = ImmutableList.of(
@@ -485,7 +509,8 @@
         // Create another appsearchImpl on the same folder
         InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
         appSearchImpl = AppSearchImpl.create(
-                folder, new UnlimitedLimitConfig(), new DefaultIcingOptionsConfig(),
+                folder, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()),
                 initStatsBuilder, ALWAYS_OPTIMIZE, /*visibilityChecker=*/null);
         InitializeStats iStats = initStatsBuilder.build();
 
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/SearchResultsImplTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/SearchResultsImplTest.java
index 6840a98..dfbc317 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/SearchResultsImplTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/SearchResultsImplTest.java
@@ -51,8 +51,10 @@
     public void setUp() throws Exception {
         mAppSearchImpl = AppSearchImpl.create(
                 mTemporaryFolder.newFolder(),
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
     }
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverterTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverterTest.java
index fa3647b..df0a1f4 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverterTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverterTest.java
@@ -19,6 +19,9 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.localstorage.AppSearchConfigImpl;
+import androidx.appsearch.localstorage.DefaultIcingOptionsConfig;
+import androidx.appsearch.localstorage.UnlimitedLimitConfig;
 
 import com.google.android.icing.proto.DocumentProto;
 import com.google.android.icing.proto.PropertyConfigProto;
@@ -63,7 +66,7 @@
                     SCHEMA_PROTO_2);
 
     @Test
-    public void testDocumentProtoConvert() {
+    public void testDocumentProtoConvert() throws Exception {
         GenericDocument document =
                 new GenericDocument.Builder<GenericDocument.Builder<?>>("namespace", "id1",
                         SCHEMA_TYPE_1)
@@ -115,7 +118,8 @@
 
         GenericDocument convertedGenericDocument =
                 GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
-                        SCHEMA_MAP);
+                        SCHEMA_MAP, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                                new DefaultIcingOptionsConfig()));
         DocumentProto convertedDocumentProto =
                 GenericDocumentToProtoConverter.toDocumentProto(document);
 
@@ -124,7 +128,7 @@
     }
 
     @Test
-    public void testConvertDocument_whenPropertyHasEmptyList() {
+    public void testConvertDocument_whenPropertyHasEmptyList() throws Exception {
         // Build original GenericDocument
         GenericDocument document =
                 new GenericDocument.Builder<GenericDocument.Builder<?>>("namespace", "id1",
@@ -212,7 +216,8 @@
         // Convert to the other type and check if they are matched.
         GenericDocument convertedGenericDocument =
                 GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
-                        schemaMap);
+                        schemaMap, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                                new DefaultIcingOptionsConfig()));
         DocumentProto convertedDocumentProto =
                 GenericDocumentToProtoConverter.toDocumentProto(document);
         assertThat(convertedDocumentProto).isEqualTo(documentProto);
@@ -220,7 +225,7 @@
     }
 
     @Test
-    public void testConvertDocument_whenNestedDocumentPropertyHasEmptyList() {
+    public void testConvertDocument_whenNestedDocumentPropertyHasEmptyList() throws Exception {
         // Build original nested document in type 1 and outer document in type2
         GenericDocument nestedDocument =
                 new GenericDocument.Builder<GenericDocument.Builder<?>>("namespace", "id1",
@@ -340,10 +345,84 @@
         // Convert to the other type and check if they are matched.
         GenericDocument convertedGenericDocument =
                 GenericDocumentToProtoConverter.toGenericDocument(outerDocumentProto, PREFIX,
-                        schemaMap);
+                        schemaMap, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                                new DefaultIcingOptionsConfig()));
         DocumentProto convertedDocumentProto =
                 GenericDocumentToProtoConverter.toDocumentProto(outerDocument);
         assertThat(convertedDocumentProto).isEqualTo(outerDocumentProto);
         assertThat(convertedGenericDocument).isEqualTo(outerDocument);
     }
+
+    @Test
+    public void testConvertDocument_withParentTypes() throws Exception {
+        // Create a type with a parent type.
+        SchemaTypeConfigProto schemaProto1 = SchemaTypeConfigProto.newBuilder()
+                .setSchemaType(PREFIX + SCHEMA_TYPE_1)
+                .addParentTypes(PREFIX + SCHEMA_TYPE_2)
+                .build();
+        Map<String, SchemaTypeConfigProto> schemaMap =
+                ImmutableMap.of(PREFIX + SCHEMA_TYPE_1, schemaProto1, PREFIX + SCHEMA_TYPE_2,
+                        SCHEMA_PROTO_2);
+
+        // Create a document proto for the above type.
+        DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder()
+                .setUri("id1")
+                .setSchema(SCHEMA_TYPE_1)
+                .setCreationTimestampMs(5L)
+                .setScore(1)
+                .setTtlMs(1L)
+                .setNamespace("namespace");
+        HashMap<String, PropertyProto.Builder> propertyProtoMap = new HashMap<>();
+        propertyProtoMap.put("longKey1",
+                PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L));
+        propertyProtoMap.put("doubleKey1",
+                PropertyProto.newBuilder().setName("doubleKey1").addDoubleValues(1.0));
+        for (Map.Entry<String, PropertyProto.Builder> entry : propertyProtoMap.entrySet()) {
+            documentProtoBuilder.addProperties(entry.getValue());
+        }
+        DocumentProto documentProto = documentProtoBuilder.build();
+
+        // Check if the parent types list is properly wrapped, either as a property or a meta field.
+        GenericDocument expectedDocWithParentAsMetaField =
+                new GenericDocument.Builder<GenericDocument.Builder<?>>("namespace", "id1",
+                        SCHEMA_TYPE_1)
+                        .setParentTypes(Collections.singletonList(SCHEMA_TYPE_2))
+                        .setCreationTimestampMillis(5L)
+                        .setScore(1)
+                        .setTtlMillis(1L)
+                        .setPropertyLong("longKey1", 1L)
+                        .setPropertyDouble("doubleKey1", 1.0)
+                        .build();
+        GenericDocument expectedDocWithParentAsSyntheticProperty =
+                new GenericDocument.Builder<GenericDocument.Builder<?>>("namespace", "id1",
+                        SCHEMA_TYPE_1)
+                        .setPropertyString(
+                                GenericDocument.PARENT_TYPES_SYNTHETIC_PROPERTY, SCHEMA_TYPE_2)
+                        .setCreationTimestampMillis(5L)
+                        .setScore(1)
+                        .setTtlMillis(1L)
+                        .setPropertyLong("longKey1", 1L)
+                        .setPropertyDouble("doubleKey1", 1.0)
+                        .build();
+
+        GenericDocument actualDocWithParentAsMetaField =
+                GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
+                        schemaMap, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                                new DefaultIcingOptionsConfig(),
+                                /* storeParentInfoAsSyntheticProperty= */ false));
+        GenericDocument actualDocWithParentAsSyntheticProperty =
+                GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
+                        schemaMap, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                                new DefaultIcingOptionsConfig(),
+                                /* storeParentInfoAsSyntheticProperty= */ true));
+
+        assertThat(actualDocWithParentAsMetaField).isEqualTo(expectedDocWithParentAsMetaField);
+        assertThat(actualDocWithParentAsMetaField).isNotEqualTo(
+                expectedDocWithParentAsSyntheticProperty);
+
+        assertThat(actualDocWithParentAsSyntheticProperty).isEqualTo(
+                expectedDocWithParentAsSyntheticProperty);
+        assertThat(actualDocWithParentAsSyntheticProperty).isNotEqualTo(
+                expectedDocWithParentAsMetaField);
+    }
 }
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverterTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverterTest.java
index 59bdf03..11e0e8e 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverterTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverterTest.java
@@ -25,6 +25,9 @@
 import androidx.appsearch.app.SearchResult;
 import androidx.appsearch.app.SearchResultPage;
 import androidx.appsearch.exceptions.AppSearchException;
+import androidx.appsearch.localstorage.AppSearchConfigImpl;
+import androidx.appsearch.localstorage.DefaultIcingOptionsConfig;
+import androidx.appsearch.localstorage.UnlimitedLimitConfig;
 import androidx.appsearch.localstorage.util.PrefixUtil;
 
 import com.google.android.icing.proto.DocumentProto;
@@ -45,6 +48,8 @@
         final String id = "id";
         final String namespace = prefix + "namespace";
         final String schemaType = prefix + "schema";
+        final AppSearchConfigImpl config = new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                new DefaultIcingOptionsConfig());
 
         // Building the SearchResult received from query.
         DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder()
@@ -78,20 +83,22 @@
 
         removePrefixesFromDocument(documentProtoBuilder);
         removePrefixesFromDocument(joinedDocProtoBuilder);
-        SearchResultPage searchResultPage =
-                SearchResultToProtoConverter.toSearchResultPage(searchResultProto, schemaMap);
+        SearchResultPage searchResultPage = SearchResultToProtoConverter.toSearchResultPage(
+                searchResultProto, schemaMap, config);
         assertThat(searchResultPage.getResults()).hasSize(1);
         SearchResult result = searchResultPage.getResults().get(0);
         assertThat(result.getPackageName()).isEqualTo("com.package.foo");
         assertThat(result.getDatabaseName()).isEqualTo("databaseName");
         assertThat(result.getGenericDocument()).isEqualTo(
                 GenericDocumentToProtoConverter.toGenericDocument(
-                        documentProtoBuilder.build(), prefix, schemaMap.get(prefix)));
+                        documentProtoBuilder.build(), prefix, schemaMap.get(prefix),
+                        config));
 
         assertThat(result.getJoinedResults()).hasSize(1);
         assertThat(result.getJoinedResults().get(0).getGenericDocument()).isEqualTo(
                 GenericDocumentToProtoConverter.toGenericDocument(
-                        joinedDocProtoBuilder.build(), prefix, schemaMap.get(prefix)));
+                        joinedDocProtoBuilder.build(), prefix, schemaMap.get(prefix),
+                        config));
     }
 
     @Test
@@ -140,8 +147,10 @@
                 ImmutableMap.of(schemaType, schemaTypeConfigProto));
 
         removePrefixesFromDocument(documentProtoBuilder);
-        Exception e = assertThrows(AppSearchException.class, () ->
-                SearchResultToProtoConverter.toSearchResultPage(searchResultProto, schemaMap));
+        Exception e = assertThrows(AppSearchException.class,
+                () -> SearchResultToProtoConverter.toSearchResultPage(searchResultProto, schemaMap,
+                        new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                                new DefaultIcingOptionsConfig())));
         assertThat(e.getMessage())
                 .isEqualTo("Nesting joined results within joined results not allowed.");
     }
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverterTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverterTest.java
index de56b69..bc4d274 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverterTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverterTest.java
@@ -25,6 +25,7 @@
 
 import androidx.appsearch.app.JoinSpec;
 import androidx.appsearch.app.SearchSpec;
+import androidx.appsearch.localstorage.AppSearchConfigImpl;
 import androidx.appsearch.localstorage.AppSearchImpl;
 import androidx.appsearch.localstorage.DefaultIcingOptionsConfig;
 import androidx.appsearch.localstorage.IcingOptionsConfig;
@@ -71,8 +72,10 @@
     public void setUp() throws Exception {
         mAppSearchImpl = AppSearchImpl.create(
                 mTemporaryFolder.newFolder(),
-                new UnlimitedLimitConfig(),
-                mDefaultIcingOptionsConfig,
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        mDefaultIcingOptionsConfig
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
@@ -496,6 +499,58 @@
     }
 
     @Test
+    public void testToSearchSpecProto_propertyFilter_withJoinSpec_packageFilter() throws Exception {
+        String personPrefix = PrefixUtil.createPrefix("contacts", "database");
+        String actionPrefix = PrefixUtil.createPrefix("aiai", "database");
+
+        SchemaTypeConfigProto configProto = SchemaTypeConfigProto.getDefaultInstance();
+        Map<String, Map<String, SchemaTypeConfigProto>> schemaMap = ImmutableMap.of(
+                personPrefix, ImmutableMap.of(personPrefix + "Person", configProto),
+                actionPrefix, ImmutableMap.of(actionPrefix + "ContactAction", configProto));
+        Map<String, Set<String>> namespaceMap = ImmutableMap.of(
+                personPrefix, ImmutableSet.of(personPrefix + "namespaceA"),
+                actionPrefix, ImmutableSet.of(actionPrefix + "namespaceA"));
+
+        SearchSpec nestedSearchSpec = new SearchSpec.Builder()
+                .setResultGrouping(SearchSpec.GROUPING_TYPE_PER_PACKAGE, 10)
+                .addFilterProperties("ContactAction", ImmutableList.of("type"))
+                .build();
+
+        // Create a JoinSpec object and set it in the converter
+        JoinSpec joinSpec = new JoinSpec.Builder("childPropertyExpression")
+                .setNestedSearch("nestedQuery", nestedSearchSpec)
+                .build();
+
+        SearchSpec searchSpec = new SearchSpec.Builder()
+                .setJoinSpec(joinSpec)
+                .setResultGrouping(SearchSpec.GROUPING_TYPE_PER_PACKAGE, 10)
+                .addFilterProperties("Person", ImmutableList.of("name"))
+                .build();
+
+        SearchSpecToProtoConverter converter = new SearchSpecToProtoConverter(
+                /*queryExpression=*/"query",
+                searchSpec,
+                /*prefixes=*/ImmutableSet.of(personPrefix, actionPrefix),
+                namespaceMap,
+                schemaMap,
+                mDefaultIcingOptionsConfig);
+
+        SearchSpecProto searchSpecProto = converter.toSearchSpecProto();
+
+        assertThat(searchSpecProto.getTypePropertyFiltersCount()).isEqualTo(1);
+        assertThat(searchSpecProto.getTypePropertyFilters(0).getSchemaType()).isEqualTo(
+                "contacts$database/Person");
+        assertThat(searchSpecProto.getTypePropertyFilters(0).getPaths(0)).isEqualTo("name");
+
+        SearchSpecProto nestedSearchSpecProto =
+                searchSpecProto.getJoinSpec().getNestedSpec().getSearchSpec();
+        assertThat(nestedSearchSpecProto.getTypePropertyFiltersCount()).isEqualTo(1);
+        assertThat(nestedSearchSpecProto.getTypePropertyFilters(0).getSchemaType()).isEqualTo(
+                "aiai$database/ContactAction");
+        assertThat(nestedSearchSpecProto.getTypePropertyFilters(0).getPaths(0)).isEqualTo("type");
+    }
+
+    @Test
     public void testToResultSpecProto_weight_withJoinSpec_packageFilter() throws Exception {
         String personPrefix = PrefixUtil.createPrefix("contacts", "database");
         String actionPrefix = PrefixUtil.createPrefix("aiai", "database");
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SnippetTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SnippetTest.java
index 9e3b559..026424f 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SnippetTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SnippetTest.java
@@ -21,6 +21,9 @@
 import androidx.appsearch.app.PropertyPath;
 import androidx.appsearch.app.SearchResult;
 import androidx.appsearch.app.SearchResultPage;
+import androidx.appsearch.localstorage.AppSearchConfigImpl;
+import androidx.appsearch.localstorage.DefaultIcingOptionsConfig;
+import androidx.appsearch.localstorage.UnlimitedLimitConfig;
 import androidx.appsearch.localstorage.util.PrefixUtil;
 
 import com.google.android.icing.proto.DocumentProto;
@@ -92,7 +95,8 @@
         // Making ResultReader and getting Snippet values.
         SearchResultPage searchResultPage = SearchResultToProtoConverter.toSearchResultPage(
                 searchResultProto,
-                SCHEMA_MAP);
+                SCHEMA_MAP, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()));
         assertThat(searchResultPage.getResults()).hasSize(1);
         SearchResult.MatchInfo match = searchResultPage.getResults().get(0).getMatchInfos().get(0);
         assertThat(match.getPropertyPath()).isEqualTo(propertyKeyString);
@@ -132,7 +136,8 @@
 
         SearchResultPage searchResultPage = SearchResultToProtoConverter.toSearchResultPage(
                 searchResultProto,
-                SCHEMA_MAP);
+                SCHEMA_MAP, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()));
         assertThat(searchResultPage.getResults()).hasSize(1);
         assertThat(searchResultPage.getResults().get(0).getMatchInfos()).isEmpty();
     }
@@ -188,7 +193,8 @@
         // Making ResultReader and getting Snippet values.
         SearchResultPage searchResultPage = SearchResultToProtoConverter.toSearchResultPage(
                 searchResultProto,
-                SCHEMA_MAP);
+                SCHEMA_MAP, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()));
         assertThat(searchResultPage.getResults()).hasSize(1);
         SearchResult.MatchInfo match1 = searchResultPage.getResults().get(0).getMatchInfos().get(0);
         assertThat(match1.getPropertyPath()).isEqualTo("senderName");
@@ -274,7 +280,8 @@
         // Making ResultReader and getting Snippet values.
         SearchResultPage searchResultPage = SearchResultToProtoConverter.toSearchResultPage(
                 searchResultProto,
-                SCHEMA_MAP);
+                SCHEMA_MAP, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()));
         assertThat(searchResultPage.getResults()).hasSize(1);
         SearchResult.MatchInfo match1 = searchResultPage.getResults().get(0).getMatchInfos().get(0);
         assertThat(match1.getPropertyPath()).isEqualTo("sender.name");
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/stats/AppSearchStatsTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/stats/AppSearchStatsTest.java
index c395e31..667dd86 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/stats/AppSearchStatsTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/stats/AppSearchStatsTest.java
@@ -86,6 +86,10 @@
         final int nativeDocumentSize = 7;
         final int nativeNumTokensIndexed = 8;
         final boolean nativeExceededMaxNumTokens = true;
+        final int nativeTermIndexLatencyMillis = 9;
+        final int nativeIntegerIndexLatencyMillis = 10;
+        final int nativeQualifiedIdJoinIndexLatencyMillis = 11;
+        final int nativeLiteIndexSortLatencyMillis = 12;
         final PutDocumentStats.Builder pStatsBuilder =
                 new PutDocumentStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE)
                         .setStatusCode(TEST_STATUS_CODE)
@@ -97,7 +101,12 @@
                         .setNativeIndexLatencyMillis(nativeIndexLatencyMillis)
                         .setNativeIndexMergeLatencyMillis(nativeIndexMergeLatencyMillis)
                         .setNativeDocumentSizeBytes(nativeDocumentSize)
-                        .setNativeNumTokensIndexed(nativeNumTokensIndexed);
+                        .setNativeNumTokensIndexed(nativeNumTokensIndexed)
+                        .setNativeTermIndexLatencyMillis(nativeTermIndexLatencyMillis)
+                        .setNativeIntegerIndexLatencyMillis(nativeIntegerIndexLatencyMillis)
+                        .setNativeQualifiedIdJoinIndexLatencyMillis(
+                                nativeQualifiedIdJoinIndexLatencyMillis)
+                        .setNativeLiteIndexSortLatencyMillis(nativeLiteIndexSortLatencyMillis);
 
         final PutDocumentStats pStats = pStatsBuilder.build();
 
@@ -118,6 +127,14 @@
                 nativeIndexMergeLatencyMillis);
         assertThat(pStats.getNativeDocumentSizeBytes()).isEqualTo(nativeDocumentSize);
         assertThat(pStats.getNativeNumTokensIndexed()).isEqualTo(nativeNumTokensIndexed);
+        assertThat(pStats.getNativeTermIndexLatencyMillis()).isEqualTo(
+                nativeTermIndexLatencyMillis);
+        assertThat(pStats.getNativeIntegerIndexLatencyMillis()).isEqualTo(
+                nativeIntegerIndexLatencyMillis);
+        assertThat(pStats.getNativeQualifiedIdJoinIndexLatencyMillis()).isEqualTo(
+                nativeQualifiedIdJoinIndexLatencyMillis);
+        assertThat(pStats.getNativeLiteIndexSortLatencyMillis()).isEqualTo(
+                nativeLiteIndexSortLatencyMillis);
     }
 
     @Test
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV0Test.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV0Test.java
index 167ed1c..d516e81 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV0Test.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV0Test.java
@@ -31,6 +31,7 @@
 import androidx.appsearch.app.InternalSetSchemaResponse;
 import androidx.appsearch.app.PackageIdentifier;
 import androidx.appsearch.app.VisibilityDocument;
+import androidx.appsearch.localstorage.AppSearchConfigImpl;
 import androidx.appsearch.localstorage.AppSearchImpl;
 import androidx.appsearch.localstorage.DefaultIcingOptionsConfig;
 import androidx.appsearch.localstorage.OptimizeStrategy;
@@ -127,8 +128,10 @@
 
         // Persist to disk and re-open the AppSearchImpl
         appSearchImplInV0.close();
-        AppSearchImpl appSearchImpl = AppSearchImpl.create(mFile, new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(), /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
+        AppSearchImpl appSearchImpl = AppSearchImpl.create(mFile,
+                new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()), /*initStatsBuilder=*/ null,
+                ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
         VisibilityDocument actualDocument1 = new VisibilityDocument(
@@ -193,8 +196,10 @@
                         .build())
                 .build();
         // Set deprecated visibility schema version 0 into AppSearchImpl.
-        AppSearchImpl appSearchImpl = AppSearchImpl.create(mFile, new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(), /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
+        AppSearchImpl appSearchImpl = AppSearchImpl.create(mFile,
+                new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()), /*initStatsBuilder=*/ null,
+                ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
         InternalSetSchemaResponse internalSetSchemaResponse = appSearchImpl.setSchema(
                 VisibilityStore.VISIBILITY_PACKAGE_NAME,
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV1Test.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV1Test.java
index 0ca32c0..56086d4 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV1Test.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV1Test.java
@@ -26,6 +26,7 @@
 import androidx.appsearch.app.PackageIdentifier;
 import androidx.appsearch.app.SetSchemaRequest;
 import androidx.appsearch.app.VisibilityDocument;
+import androidx.appsearch.localstorage.AppSearchConfigImpl;
 import androidx.appsearch.localstorage.AppSearchImpl;
 import androidx.appsearch.localstorage.DefaultIcingOptionsConfig;
 import androidx.appsearch.localstorage.OptimizeStrategy;
@@ -71,8 +72,10 @@
         byte[] sha256CertBar = new byte[32];
 
         // Create AppSearchImpl with visibility document version 1;
-        AppSearchImpl appSearchImplInV1 = AppSearchImpl.create(mFile, new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(), /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
+        AppSearchImpl appSearchImplInV1 = AppSearchImpl.create(mFile,
+                new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()), /*initStatsBuilder=*/ null,
+                ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
         InternalSetSchemaResponse internalSetSchemaResponse = appSearchImplInV1.setSchema(
                 VisibilityStore.VISIBILITY_PACKAGE_NAME,
@@ -119,8 +122,10 @@
 
         // Persist to disk and re-open the AppSearchImpl
         appSearchImplInV1.close();
-        AppSearchImpl appSearchImpl = AppSearchImpl.create(mFile, new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(), /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
+        AppSearchImpl appSearchImpl = AppSearchImpl.create(mFile,
+                new AppSearchConfigImpl(new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()), /*initStatsBuilder=*/ null,
+                ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
         VisibilityDocument actualDocument = new VisibilityDocument(
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreTest.java
index 99a1ffe..721b3ad 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreTest.java
@@ -25,6 +25,7 @@
 import androidx.appsearch.app.PackageIdentifier;
 import androidx.appsearch.app.VisibilityDocument;
 import androidx.appsearch.exceptions.AppSearchException;
+import androidx.appsearch.localstorage.AppSearchConfigImpl;
 import androidx.appsearch.localstorage.AppSearchImpl;
 import androidx.appsearch.localstorage.DefaultIcingOptionsConfig;
 import androidx.appsearch.localstorage.OptimizeStrategy;
@@ -60,8 +61,10 @@
         mAppSearchDir = mTemporaryFolder.newFolder();
         mAppSearchImpl = AppSearchImpl.create(
                 mAppSearchDir,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
index 120a025..9b71dde 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
@@ -67,6 +67,8 @@
             case Features.SCHEMA_ADD_PARENT_TYPE:
                 // fall through
             case Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES:
+                // fall through
+            case Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES:
                 return true;
             default:
                 return false;
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfig.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfig.java
new file mode 100644
index 0000000..811f04d
--- /dev/null
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfig.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appsearch.localstorage;
+
+import androidx.annotation.RestrictTo;
+import androidx.appsearch.app.GenericDocument;
+
+/**
+ * An interface that wraps AppSearch configurations required to create {@link AppSearchImpl}.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public interface AppSearchConfig extends IcingOptionsConfig, LimitConfig {
+
+    /**
+     * Whether to store {@link GenericDocument}'s parent types as a synthetic property. If not,
+     * the list of parent types will be wrapped as a meta field in {@link GenericDocument}, in a
+     * similar way as namespace, id, creationTimestamp, etc.
+     */
+    boolean shouldStoreParentInfoAsSyntheticProperty();
+}
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfigImpl.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfigImpl.java
new file mode 100644
index 0000000..c29800c
--- /dev/null
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfigImpl.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// @exportToFramework:copyToPath(testing/testutils/src/android/app/appsearch/testutil/external/AppSearchConfigImpl.java)
+package androidx.appsearch.localstorage;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+/**
+ * An implementation of AppSearchConfig that returns configurations based what is specified in
+ * constructor.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class AppSearchConfigImpl implements AppSearchConfig {
+    private final LimitConfig mLimitConfig;
+    private final IcingOptionsConfig mIcingOptionsConfig;
+    private final boolean mStoreParentInfoAsSyntheticProperty;
+
+    public AppSearchConfigImpl(@NonNull LimitConfig limitConfig,
+            @NonNull IcingOptionsConfig icingOptionsConfig) {
+        this(limitConfig, icingOptionsConfig, false);
+    }
+
+    public AppSearchConfigImpl(@NonNull LimitConfig limitConfig,
+            @NonNull IcingOptionsConfig icingOptionsConfig,
+            boolean storeParentInfoAsSyntheticProperty) {
+        mLimitConfig = limitConfig;
+        mIcingOptionsConfig = icingOptionsConfig;
+        mStoreParentInfoAsSyntheticProperty = storeParentInfoAsSyntheticProperty;
+    }
+
+    @Override
+    public int getMaxTokenLength() {
+        return mIcingOptionsConfig.getMaxTokenLength();
+    }
+
+    @Override
+    public int getIndexMergeSize() {
+        return mIcingOptionsConfig.getIndexMergeSize();
+    }
+
+    @Override
+    public boolean getDocumentStoreNamespaceIdFingerprint() {
+        return mIcingOptionsConfig.getDocumentStoreNamespaceIdFingerprint();
+    }
+
+    @Override
+    public float getOptimizeRebuildIndexThreshold() {
+        return mIcingOptionsConfig.getOptimizeRebuildIndexThreshold();
+    }
+
+    @Override
+    public int getCompressionLevel() {
+        return mIcingOptionsConfig.getCompressionLevel();
+    }
+
+    @Override
+    public boolean getAllowCircularSchemaDefinitions() {
+        return mIcingOptionsConfig.getAllowCircularSchemaDefinitions();
+    }
+
+    @Override
+    public boolean getUseReadOnlySearch() {
+        return mIcingOptionsConfig.getUseReadOnlySearch();
+    }
+
+    @Override
+    public boolean getUsePreMappingWithFileBackedVector() {
+        return mIcingOptionsConfig.getUsePreMappingWithFileBackedVector();
+    }
+
+    @Override
+    public boolean getUsePersistentHashMap() {
+        return mIcingOptionsConfig.getUsePersistentHashMap();
+    }
+
+    @Override
+    public int getMaxPageBytesLimit() {
+        return mIcingOptionsConfig.getMaxPageBytesLimit();
+    }
+
+    @Override
+    public int getIntegerIndexBucketSplitThreshold() {
+        return mIcingOptionsConfig.getIntegerIndexBucketSplitThreshold();
+    }
+
+    @Override
+    public boolean getLiteIndexSortAtIndexing() {
+        return mIcingOptionsConfig.getLiteIndexSortAtIndexing();
+    }
+
+    @Override
+    public int getLiteIndexSortSize() {
+        return mIcingOptionsConfig.getLiteIndexSortSize();
+    }
+
+    @Override
+    public int getMaxDocumentSizeBytes() {
+        return mLimitConfig.getMaxDocumentSizeBytes();
+    }
+
+    @Override
+    public int getMaxDocumentCount() {
+        return mLimitConfig.getMaxDocumentCount();
+    }
+
+    @Override
+    public int getMaxSuggestionCount() {
+        return mLimitConfig.getMaxSuggestionCount();
+    }
+
+    @Override
+    public boolean shouldStoreParentInfoAsSyntheticProperty() {
+        return mStoreParentInfoAsSyntheticProperty;
+    }
+}
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
index c819c45..68fa3b7 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
@@ -181,8 +181,7 @@
 
     private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
     private final OptimizeStrategy mOptimizeStrategy;
-    private final LimitConfig mLimitConfig;
-    private final IcingOptionsConfig mIcingOptionsConfig;
+    private final AppSearchConfig mConfig;
 
     @GuardedBy("mReadWriteLock")
     @VisibleForTesting
@@ -268,14 +267,13 @@
     @NonNull
     public static AppSearchImpl create(
             @NonNull File icingDir,
-            @NonNull LimitConfig limitConfig,
-            @NonNull IcingOptionsConfig icingOptionsConfig,
+            @NonNull AppSearchConfig config,
             @Nullable InitializeStats.Builder initStatsBuilder,
             @NonNull OptimizeStrategy optimizeStrategy,
             @Nullable VisibilityChecker visibilityChecker)
             throws AppSearchException {
-        return new AppSearchImpl(icingDir, limitConfig, icingOptionsConfig, initStatsBuilder,
-                optimizeStrategy, visibilityChecker);
+        return new AppSearchImpl(icingDir, config, initStatsBuilder, optimizeStrategy,
+                visibilityChecker);
     }
 
     /**
@@ -283,15 +281,13 @@
      */
     private AppSearchImpl(
             @NonNull File icingDir,
-            @NonNull LimitConfig limitConfig,
-            @NonNull IcingOptionsConfig icingOptionsConfig,
+            @NonNull AppSearchConfig config,
             @Nullable InitializeStats.Builder initStatsBuilder,
             @NonNull OptimizeStrategy optimizeStrategy,
             @Nullable VisibilityChecker visibilityChecker)
             throws AppSearchException {
         Preconditions.checkNotNull(icingDir);
-        mLimitConfig = Preconditions.checkNotNull(limitConfig);
-        mIcingOptionsConfig = Preconditions.checkNotNull(icingOptionsConfig);
+        mConfig = Preconditions.checkNotNull(config);
         mOptimizeStrategy = Preconditions.checkNotNull(optimizeStrategy);
         mVisibilityCheckerLocked = visibilityChecker;
 
@@ -301,19 +297,21 @@
             // than once. It's unnecessary and can be a costly operation.
             IcingSearchEngineOptions options = IcingSearchEngineOptions.newBuilder()
                     .setBaseDir(icingDir.getAbsolutePath())
-                    .setMaxTokenLength(icingOptionsConfig.getMaxTokenLength())
-                    .setIndexMergeSize(icingOptionsConfig.getIndexMergeSize())
+                    .setMaxTokenLength(mConfig.getMaxTokenLength())
+                    .setIndexMergeSize(mConfig.getIndexMergeSize())
                     .setDocumentStoreNamespaceIdFingerprint(
-                            icingOptionsConfig.getDocumentStoreNamespaceIdFingerprint())
+                            mConfig.getDocumentStoreNamespaceIdFingerprint())
                     .setOptimizeRebuildIndexThreshold(
-                            icingOptionsConfig.getOptimizeRebuildIndexThreshold())
-                    .setCompressionLevel(icingOptionsConfig.getCompressionLevel())
+                            mConfig.getOptimizeRebuildIndexThreshold())
+                    .setCompressionLevel(mConfig.getCompressionLevel())
                     .setAllowCircularSchemaDefinitions(
-                            icingOptionsConfig.getAllowCircularSchemaDefinitions())
-                    .setPreMappingFbv(icingOptionsConfig.getUsePreMappingWithFileBackedVector())
-                    .setUsePersistentHashMap(icingOptionsConfig.getUsePersistentHashMap())
+                            mConfig.getAllowCircularSchemaDefinitions())
+                    .setPreMappingFbv(mConfig.getUsePreMappingWithFileBackedVector())
+                    .setUsePersistentHashMap(mConfig.getUsePersistentHashMap())
                     .setIntegerIndexBucketSplitThreshold(
-                            icingOptionsConfig.getIntegerIndexBucketSplitThreshold())
+                            mConfig.getIntegerIndexBucketSplitThreshold())
+                    .setLiteIndexSortAtIndexing(mConfig.getLiteIndexSortAtIndexing())
+                    .setLiteIndexSortSize(mConfig.getLiteIndexSortSize())
                     .build();
             LogUtil.piiTrace(TAG, "Constructing IcingSearchEngine, request", options);
             mIcingSearchEngineLocked = new IcingSearchEngine(options);
@@ -1092,12 +1090,12 @@
     private int enforceLimitConfigLocked(String packageName, String newDocUri, int newDocSize)
             throws AppSearchException {
         // Limits check: size of document
-        if (newDocSize > mLimitConfig.getMaxDocumentSizeBytes()) {
+        if (newDocSize > mConfig.getMaxDocumentSizeBytes()) {
             throw new AppSearchException(
                     AppSearchResult.RESULT_OUT_OF_SPACE,
                     "Document \"" + newDocUri + "\" for package \"" + packageName
                             + "\" serialized to " + newDocSize + " bytes, which exceeds "
-                            + "limit of " + mLimitConfig.getMaxDocumentSizeBytes() + " bytes");
+                            + "limit of " + mConfig.getMaxDocumentSizeBytes() + " bytes");
         }
 
         // Limits check: number of documents
@@ -1108,7 +1106,7 @@
         } else {
             newDocumentCount = oldDocumentCount + 1;
         }
-        if (newDocumentCount > mLimitConfig.getMaxDocumentCount()) {
+        if (newDocumentCount > mConfig.getMaxDocumentCount()) {
             // Our management of mDocumentCountMapLocked doesn't account for document
             // replacements, so our counter might have overcounted if the app has replaced docs.
             // Rebuild the counter from StorageInfo in case this is so.
@@ -1123,12 +1121,12 @@
                 newDocumentCount = oldDocumentCount + 1;
             }
         }
-        if (newDocumentCount > mLimitConfig.getMaxDocumentCount()) {
+        if (newDocumentCount > mConfig.getMaxDocumentCount()) {
             // Now we really can't fit it in, even accounting for replacements.
             throw new AppSearchException(
                     AppSearchResult.RESULT_OUT_OF_SPACE,
                     "Package \"" + packageName + "\" exceeded limit of "
-                            + mLimitConfig.getMaxDocumentCount() + " documents. Some documents "
+                            + mConfig.getMaxDocumentCount() + " documents. Some documents "
                             + "must be removed to index additional ones.");
         }
 
@@ -1193,7 +1191,7 @@
             Map<String, SchemaTypeConfigProto> schemaTypeMap =
                     Preconditions.checkNotNull(mSchemaMapLocked.get(prefix));
             return GenericDocumentToProtoConverter.toGenericDocument(documentBuilder.build(),
-                    prefix, schemaTypeMap);
+                    prefix, schemaTypeMap, mConfig);
         } finally {
             mReadWriteLock.readLock().unlock();
         }
@@ -1235,7 +1233,7 @@
             Map<String, SchemaTypeConfigProto> schemaTypeMap =
                     Preconditions.checkNotNull(mSchemaMapLocked.get(prefix));
             return GenericDocumentToProtoConverter.toGenericDocument(documentBuilder.build(),
-                    prefix, schemaTypeMap);
+                    prefix, schemaTypeMap, mConfig);
         } finally {
             mReadWriteLock.readLock().unlock();
         }
@@ -1346,7 +1344,7 @@
             SearchSpecToProtoConverter searchSpecToProtoConverter =
                     new SearchSpecToProtoConverter(queryExpression, searchSpec,
                             Collections.singleton(prefix), mNamespaceMapLocked, mSchemaMapLocked,
-                            mIcingOptionsConfig);
+                            mConfig);
             if (searchSpecToProtoConverter.hasNothingToSearch()) {
                 // there is nothing to search over given their search filters, so we can return an
                 // empty SearchResult and skip sending request to Icing.
@@ -1446,7 +1444,7 @@
             }
             SearchSpecToProtoConverter searchSpecToProtoConverter =
                     new SearchSpecToProtoConverter(queryExpression, searchSpec, prefixFilters,
-                            mNamespaceMapLocked, mSchemaMapLocked, mIcingOptionsConfig);
+                            mNamespaceMapLocked, mSchemaMapLocked, mConfig);
             // Remove those inaccessible schemas.
             searchSpecToProtoConverter.removeInaccessibleSchemaFilter(
                     callerAccess, mVisibilityStoreLocked, mVisibilityCheckerLocked);
@@ -1501,7 +1499,7 @@
         long rewriteSearchResultLatencyStartMillis = SystemClock.elapsedRealtime();
         // Rewrite search result before we return.
         SearchResultPage searchResultPage = SearchResultToProtoConverter
-                .toSearchResultPage(searchResultProto, mSchemaMapLocked);
+                .toSearchResultPage(searchResultProto, mSchemaMapLocked, mConfig);
         if (sStatsBuilder != null) {
             sStatsBuilder.setRewriteSearchResultLatencyMillis(
                     (int) (SystemClock.elapsedRealtime()
@@ -1568,12 +1566,12 @@
                         "suggestionQueryExpression cannot be empty.");
             }
             if (searchSuggestionSpec.getMaximumResultCount()
-                    > mLimitConfig.getMaxSuggestionCount()) {
+                    > mConfig.getMaxSuggestionCount()) {
                 throw new AppSearchException(
                         AppSearchResult.RESULT_INVALID_ARGUMENT,
                         "Trying to get " + searchSuggestionSpec.getMaximumResultCount()
                                 + " suggestion results, which exceeds limit of "
-                                + mLimitConfig.getMaxSuggestionCount());
+                                + mConfig.getMaxSuggestionCount());
             }
 
             String prefix = createPrefix(packageName, databaseName);
@@ -1697,7 +1695,7 @@
             long rewriteSearchResultLatencyStartMillis = SystemClock.elapsedRealtime();
             // Rewrite search result before we return.
             SearchResultPage searchResultPage = SearchResultToProtoConverter
-                    .toSearchResultPage(searchResultProto, mSchemaMapLocked);
+                    .toSearchResultPage(searchResultProto, mSchemaMapLocked, mConfig);
             if (sStatsBuilder != null) {
                 sStatsBuilder.setRewriteSearchResultLatencyMillis(
                         (int) (SystemClock.elapsedRealtime()
@@ -1912,7 +1910,7 @@
             SearchSpecToProtoConverter searchSpecToProtoConverter =
                     new SearchSpecToProtoConverter(queryExpression, searchSpec,
                             Collections.singleton(prefix), mNamespaceMapLocked, mSchemaMapLocked,
-                            mIcingOptionsConfig);
+                            mConfig);
             if (searchSpecToProtoConverter.hasNothingToSearch()) {
                 // there is nothing to search over given their search filters, so we can return
                 // early and skip sending request to Icing.
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchLoggerHelper.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchLoggerHelper.java
index 6fb135a..652fc94 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchLoggerHelper.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchLoggerHelper.java
@@ -64,7 +64,13 @@
                 .setNativeIndexMergeLatencyMillis(fromNativeStats.getIndexMergeLatencyMs())
                 .setNativeDocumentSizeBytes(fromNativeStats.getDocumentSize())
                 .setNativeNumTokensIndexed(
-                        fromNativeStats.getTokenizationStats().getNumTokensIndexed());
+                        fromNativeStats.getTokenizationStats().getNumTokensIndexed())
+                .setNativeTermIndexLatencyMillis(fromNativeStats.getTermIndexLatencyMs())
+                .setNativeIntegerIndexLatencyMillis(fromNativeStats.getIntegerIndexLatencyMs())
+                .setNativeQualifiedIdJoinIndexLatencyMillis(
+                        fromNativeStats.getQualifiedIdJoinIndexLatencyMs())
+                .setNativeLiteIndexSortLatencyMillis(
+                        fromNativeStats.getLiteIndexSortLatencyMs());
     }
 
     /**
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/DefaultIcingOptionsConfig.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/DefaultIcingOptionsConfig.java
index ac9742d..4201c4e 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/DefaultIcingOptionsConfig.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/DefaultIcingOptionsConfig.java
@@ -78,4 +78,14 @@
     public int getIntegerIndexBucketSplitThreshold() {
         return DEFAULT_INTEGER_INDEX_BUCKET_SPLIT_THRESHOLD;
     }
+
+    @Override
+    public boolean getLiteIndexSortAtIndexing() {
+        return DEFAULT_LITE_INDEX_SORT_AT_INDEXING;
+    }
+
+    @Override
+    public int getLiteIndexSortSize() {
+        return DEFAULT_LITE_INDEX_SORT_SIZE;
+    }
 }
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/IcingOptionsConfig.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/IcingOptionsConfig.java
index 0f929f4..418569a 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/IcingOptionsConfig.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/IcingOptionsConfig.java
@@ -62,6 +62,14 @@
      */
     int DEFAULT_INTEGER_INDEX_BUCKET_SPLIT_THRESHOLD = 65536;
 
+    boolean DEFAULT_LITE_INDEX_SORT_AT_INDEXING = false;
+
+    /**
+     * The default sort threshold for the lite index when sort at indexing is enabled.
+     * 8192 is picked based on Icing microbenchmarks (icing-search-engine_benchmarks.cc).
+     */
+    int DEFAULT_LITE_INDEX_SORT_SIZE = 8192;   // 8Kib
+
     /**
      * The maximum allowable token length. All tokens in excess of this size will be truncated to
      * max_token_length before being indexed.
@@ -175,4 +183,27 @@
      * list).
      */
     int getIntegerIndexBucketSplitThreshold();
+
+    /**
+     * Flag for {@link com.google.android.icing.proto.IcingSearchEngineOptions}.
+     *
+     * <p>Whether Icing should sort and merge its lite index HitBuffer unsorted tail at indexing
+     * time.
+     *
+     * <p>If set to true, the HitBuffer will be sorted at indexing time after exceeding the sort
+     * threshold. If false, the HifBuffer will be sorted at querying time, before the first query
+     * after inserting new elements into the HitBuffer.
+     */
+    boolean getLiteIndexSortAtIndexing();
+
+    /**
+     * Flag for {@link com.google.android.icing.proto.IcingSearchEngineOptions}.
+     *
+     * <p>Size (in bytes) at which Icing's lite index should sort and merge the HitBuffer's
+     * unsorted tail into the sorted head for sorting at indexing time. Size specified here is
+     * unsorted tail section.
+     *
+     * <p>Setting a lower sort size reduces querying latency at the expense of indexing latency.
+     */
+    int getLiteIndexSortSize();
 }
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
index 86c825a..3d62ed9 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
@@ -339,8 +339,10 @@
         AppSearchImpl.syncLoggingLevelToIcing();
         mAppSearchImpl = AppSearchImpl.create(
                 icingDir,
-                new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig(),
+                new AppSearchConfigImpl(
+                        new UnlimitedLimitConfig(),
+                        new DefaultIcingOptionsConfig()
+                ),
                 initStatsBuilder,
                 new JetpackOptimizeStrategy(),
                 /*visibilityChecker=*/null);
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverter.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverter.java
index e6a7506..56c52b3 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverter.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverter.java
@@ -20,6 +20,11 @@
 import androidx.annotation.RestrictTo;
 import androidx.appsearch.app.AppSearchSchema;
 import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.exceptions.AppSearchException;
+import androidx.appsearch.localstorage.AppSearchConfig;
+import androidx.appsearch.localstorage.util.PrefixUtil;
+import androidx.collection.ArrayMap;
+import androidx.collection.ArraySet;
 import androidx.core.util.Preconditions;
 
 import com.google.android.icing.proto.DocumentProto;
@@ -28,9 +33,13 @@
 import com.google.android.icing.proto.SchemaTypeConfigProto;
 import com.google.android.icing.protobuf.ByteString;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
 
 /**
  * Translates a {@link GenericDocument} into a {@link DocumentProto}.
@@ -132,7 +141,8 @@
     @NonNull
     public static GenericDocument toGenericDocument(@NonNull DocumentProtoOrBuilder proto,
             @NonNull String prefix,
-            @NonNull Map<String, SchemaTypeConfigProto> schemaTypeMap) {
+            @NonNull Map<String, SchemaTypeConfigProto> schemaTypeMap,
+            @NonNull AppSearchConfig config) throws AppSearchException {
         Preconditions.checkNotNull(proto);
         GenericDocument.Builder<?> documentBuilder =
                 new GenericDocument.Builder<>(proto.getNamespace(), proto.getUri(),
@@ -141,6 +151,16 @@
                         .setTtlMillis(proto.getTtlMs())
                         .setCreationTimestampMillis(proto.getCreationTimestampMs());
         String prefixedSchemaType = prefix + proto.getSchema();
+        List<String> parentSchemaTypes = getUnprefixedParentSchemaTypes(
+                prefixedSchemaType, schemaTypeMap);
+        if (!parentSchemaTypes.isEmpty()) {
+            if (config.shouldStoreParentInfoAsSyntheticProperty()) {
+                documentBuilder.setPropertyString(GenericDocument.PARENT_TYPES_SYNTHETIC_PROPERTY,
+                        parentSchemaTypes.toArray(new String[0]));
+            } else {
+                documentBuilder.setParentTypes(parentSchemaTypes);
+            }
+        }
 
         for (int i = 0; i < proto.getPropertiesCount(); i++) {
             PropertyProto property = proto.getProperties(i);
@@ -179,7 +199,7 @@
                 GenericDocument[] values = new GenericDocument[property.getDocumentValuesCount()];
                 for (int j = 0; j < values.length; j++) {
                     values[j] = toGenericDocument(property.getDocumentValues(j), prefix,
-                            schemaTypeMap);
+                            schemaTypeMap, config);
                 }
                 documentBuilder.setPropertyDocument(name, values);
             } else {
@@ -192,6 +212,66 @@
         return documentBuilder.build();
     }
 
+    /**
+     * Get the list of unprefixed parent type names of {@code prefixedSchemaType}.
+     *
+     * <p>It's guaranteed that child types always appear before parent types in the list.
+     */
+    // TODO(b/290389974): Consider caching the result based prefixedSchemaType, and reset the
+    //  cache whenever a new setSchema is called.
+    @NonNull
+    private static List<String> getUnprefixedParentSchemaTypes(
+            @NonNull String prefixedSchemaType,
+            @NonNull Map<String, SchemaTypeConfigProto> schemaTypeMap) throws AppSearchException {
+        // Please note that neither DFS nor BFS order is guaranteed to always put child types
+        // before parent types (due to the diamond problem), so a topological sorting algorithm
+        // is required.
+        Map<String, Integer> inDegreeMap = new ArrayMap<>();
+        collectParentTypeInDegrees(prefixedSchemaType, schemaTypeMap,
+                /* visited= */new ArraySet<>(), inDegreeMap);
+
+        List<String> result = new ArrayList<>();
+        Queue<String> queue = new ArrayDeque<>();
+        // prefixedSchemaType is the only type that has zero in-degree at this point.
+        queue.add(prefixedSchemaType);
+        while (!queue.isEmpty()) {
+            SchemaTypeConfigProto currentSchema = Preconditions.checkNotNull(
+                    schemaTypeMap.get(queue.poll()));
+            for (int i = 0; i < currentSchema.getParentTypesCount(); ++i) {
+                String prefixedParentType = currentSchema.getParentTypes(i);
+                int parentInDegree =
+                        Preconditions.checkNotNull(inDegreeMap.get(prefixedParentType)) - 1;
+                inDegreeMap.put(prefixedParentType, parentInDegree);
+                if (parentInDegree == 0) {
+                    result.add(PrefixUtil.removePrefix(prefixedParentType));
+                    queue.add(prefixedParentType);
+                }
+            }
+        }
+        return result;
+    }
+
+    private static void collectParentTypeInDegrees(
+            @NonNull String prefixedSchemaType,
+            @NonNull Map<String, SchemaTypeConfigProto> schemaTypeMap,
+            @NonNull Set<String> visited, @NonNull Map<String, Integer> inDegreeMap) {
+        if (visited.contains(prefixedSchemaType)) {
+            return;
+        }
+        visited.add(prefixedSchemaType);
+        SchemaTypeConfigProto schema =
+                Preconditions.checkNotNull(schemaTypeMap.get(prefixedSchemaType));
+        for (int i = 0; i < schema.getParentTypesCount(); ++i) {
+            String prefixedParentType = schema.getParentTypes(i);
+            Integer parentInDegree = inDegreeMap.get(prefixedParentType);
+            if (parentInDegree == null) {
+                parentInDegree = 0;
+            }
+            inDegreeMap.put(prefixedParentType, parentInDegree + 1);
+            collectParentTypeInDegrees(prefixedParentType, schemaTypeMap, visited, inDegreeMap);
+        }
+    }
+
     private static void setEmptyProperty(@NonNull String propertyName,
             @NonNull GenericDocument.Builder<?> documentBuilder,
             @NonNull SchemaTypeConfigProto schema) {
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverter.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverter.java
index b5f0b97..e21213f 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverter.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SchemaToProtoConverter.java
@@ -101,7 +101,11 @@
                         .setValueType(
                                 convertJoinableValueTypeToProto(
                                         stringProperty.getJoinableValueType()))
+                        // @exportToFramework:startStrip()
+                        // Do not call this in framework as it will populate the proto field and
+                        // fail comparison tests.
                         .setPropagateDelete(stringProperty.getDeletionPropagation())
+                        // @exportToFramework:endStrip()
                         .build();
                 builder.setJoinableConfig(joinableConfig);
             }
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverter.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverter.java
index b26954b..8652cca 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverter.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverter.java
@@ -29,6 +29,7 @@
 import androidx.appsearch.app.SearchResult;
 import androidx.appsearch.app.SearchResultPage;
 import androidx.appsearch.exceptions.AppSearchException;
+import androidx.appsearch.localstorage.AppSearchConfig;
 import androidx.core.util.Preconditions;
 
 import com.google.android.icing.proto.DocumentProto;
@@ -60,13 +61,14 @@
      */
     @NonNull
     public static SearchResultPage toSearchResultPage(@NonNull SearchResultProto proto,
-            @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap)
+            @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap,
+            @NonNull AppSearchConfig config)
             throws AppSearchException {
         Bundle bundle = new Bundle();
         bundle.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, proto.getNextPageToken());
         ArrayList<Bundle> resultBundles = new ArrayList<>(proto.getResultsCount());
         for (int i = 0; i < proto.getResultsCount(); i++) {
-            SearchResult result = toUnprefixedSearchResult(proto.getResults(i), schemaMap);
+            SearchResult result = toUnprefixedSearchResult(proto.getResults(i), schemaMap, config);
             resultBundles.add(result.getBundle());
         }
         bundle.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles);
@@ -85,8 +87,8 @@
     @NonNull
     private static SearchResult toUnprefixedSearchResult(
             @NonNull SearchResultProto.ResultProto proto,
-            @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap)
-            throws AppSearchException {
+            @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap,
+            @NonNull AppSearchConfig config) throws AppSearchException {
 
         DocumentProto.Builder documentBuilder = proto.getDocument().toBuilder();
         String prefix = removePrefixesFromDocument(documentBuilder);
@@ -94,7 +96,7 @@
                 Preconditions.checkNotNull(schemaMap.get(prefix));
         GenericDocument document =
                 GenericDocumentToProtoConverter.toGenericDocument(documentBuilder, prefix,
-                        schemaTypeMap);
+                        schemaTypeMap, config);
         SearchResult.Builder builder =
                 new SearchResult.Builder(getPackageName(prefix), getDatabaseName(prefix))
                         .setGenericDocument(document).setRankingSignal(proto.getScore());
@@ -116,7 +118,7 @@
                         "Nesting joined results within joined results not allowed.");
             }
 
-            builder.addJoinedResult(toUnprefixedSearchResult(joinedResultProto, schemaMap));
+            builder.addJoinedResult(toUnprefixedSearchResult(joinedResultProto, schemaMap, config));
         }
         return builder.build();
     }
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverter.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverter.java
index b7d648b..981ea944 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverter.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverter.java
@@ -291,6 +291,27 @@
                 .addAllSchemaTypeFilters(mTargetPrefixedSchemaFilters)
                 .setUseReadOnlySearch(mIcingOptionsConfig.getUseReadOnlySearch());
 
+        // Convert type property filter map into type property mask proto.
+        for (Map.Entry<String, List<String>> entry :
+                mSearchSpec.getFilterProperties().entrySet()) {
+            if (entry.getKey().equals(SearchSpec.SCHEMA_TYPE_WILDCARD)) {
+                protoBuilder.addTypePropertyFilters(TypePropertyMask.newBuilder()
+                        .setSchemaType(SearchSpec.SCHEMA_TYPE_WILDCARD)
+                        .addAllPaths(entry.getValue())
+                        .build());
+            } else {
+                for (String prefix : mCurrentSearchSpecPrefixFilters) {
+                    String prefixedSchemaType = prefix + entry.getKey();
+                    if (mTargetPrefixedSchemaFilters.contains(prefixedSchemaType)) {
+                        protoBuilder.addTypePropertyFilters(TypePropertyMask.newBuilder()
+                                .setSchemaType(prefixedSchemaType)
+                                .addAllPaths(entry.getValue())
+                                .build());
+                    }
+                }
+            }
+        }
+
         @SearchSpec.TermMatch int termMatchCode = mSearchSpec.getTermMatch();
         TermMatchType.Code termMatchCodeProto = TermMatchType.Code.forNumber(termMatchCode);
         if (termMatchCodeProto == null || termMatchCodeProto.equals(TermMatchType.Code.UNKNOWN)) {
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/stats/PutDocumentStats.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/stats/PutDocumentStats.java
index 65c5923..15c6e32 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/stats/PutDocumentStats.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/stats/PutDocumentStats.java
@@ -66,6 +66,21 @@
     /** Number of tokens added to the index. */
     private final int mNativeNumTokensIndexed;
 
+    /**
+     * Time used to index all indexable string terms in the document. It does not include the
+     * time to merge indices.
+     */
+    private final int mNativeTermIndexLatencyMillis;
+
+    /** Time used to index all indexable integers in the document. */
+    private final int mNativeIntegerIndexLatencyMillis;
+
+    /** Time used to index all qualified id join strings in the document. */
+    private final int mNativeQualifiedIdJoinIndexLatencyMillis;
+
+    /** Time used to sort and merge the lite index's hit buffer. */
+    private final int mNativeLiteIndexSortLatencyMillis;
+
     PutDocumentStats(@NonNull Builder builder) {
         Preconditions.checkNotNull(builder);
         mPackageName = builder.mPackageName;
@@ -80,6 +95,10 @@
         mNativeIndexMergeLatencyMillis = builder.mNativeIndexMergeLatencyMillis;
         mNativeDocumentSizeBytes = builder.mNativeDocumentSizeBytes;
         mNativeNumTokensIndexed = builder.mNativeNumTokensIndexed;
+        mNativeTermIndexLatencyMillis = builder.mNativeTermIndexLatencyMillis;
+        mNativeIntegerIndexLatencyMillis = builder.mNativeIntegerIndexLatencyMillis;
+        mNativeQualifiedIdJoinIndexLatencyMillis = builder.mNativeQualifiedIdJoinIndexLatencyMillis;
+        mNativeLiteIndexSortLatencyMillis = builder.mNativeLiteIndexSortLatencyMillis;
     }
 
     /** Returns calling package name. */
@@ -145,6 +164,26 @@
         return mNativeNumTokensIndexed;
     }
 
+    /** Returns time spent on term indexing, in milliseconds. */
+    public int getNativeTermIndexLatencyMillis() {
+        return mNativeTermIndexLatencyMillis;
+    }
+
+    /** Returns time spent on integer indexing, in milliseconds. */
+    public int getNativeIntegerIndexLatencyMillis() {
+        return mNativeIntegerIndexLatencyMillis;
+    }
+
+    /** Returns time spent on qualified id join indexing, in milliseconds. */
+    public int getNativeQualifiedIdJoinIndexLatencyMillis() {
+        return mNativeQualifiedIdJoinIndexLatencyMillis;
+    }
+
+    /** Returns time spent sorting and merging the lite index, in milliseconds. */
+    public int getNativeLiteIndexSortLatencyMillis() {
+        return mNativeLiteIndexSortLatencyMillis;
+    }
+
     /** Builder for {@link PutDocumentStats}. */
     public static class Builder {
         @NonNull
@@ -162,6 +201,10 @@
         int mNativeIndexMergeLatencyMillis;
         int mNativeDocumentSizeBytes;
         int mNativeNumTokensIndexed;
+        int mNativeTermIndexLatencyMillis;
+        int mNativeIntegerIndexLatencyMillis;
+        int mNativeQualifiedIdJoinIndexLatencyMillis;
+        int mNativeLiteIndexSortLatencyMillis;
 
         /** Builder for {@link PutDocumentStats} */
         public Builder(@NonNull String packageName, @NonNull String database) {
@@ -253,6 +296,39 @@
             return this;
         }
 
+        /** Sets the native term indexing time, in millis. */
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder setNativeTermIndexLatencyMillis(int nativeTermIndexLatencyMillis) {
+            mNativeTermIndexLatencyMillis = nativeTermIndexLatencyMillis;
+            return this;
+        }
+
+        /** Sets the native integer indexing time, in millis. */
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder setNativeIntegerIndexLatencyMillis(int nativeIntegerIndexLatencyMillis) {
+            mNativeIntegerIndexLatencyMillis = nativeIntegerIndexLatencyMillis;
+            return this;
+        }
+
+        /** Sets the native qualified id indexing time, in millis. */
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder setNativeQualifiedIdJoinIndexLatencyMillis(
+                int nativeQualifiedIdJoinIndexLatencyMillis) {
+            mNativeQualifiedIdJoinIndexLatencyMillis = nativeQualifiedIdJoinIndexLatencyMillis;
+            return this;
+        }
+
+        /** Sets the native lite index sort latency, in millis. */
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder setNativeLiteIndexSortLatencyMillis(int nativeLiteIndexSortLatencyMillis) {
+            mNativeLiteIndexSortLatencyMillis = nativeLiteIndexSortLatencyMillis;
+            return this;
+        }
+
         /**
          * Creates a new {@link PutDocumentStats} object from the contents of this
          * {@link Builder} instance.
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
index ed3a8f6..4095f32 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
@@ -74,6 +74,9 @@
                 // fall through
             case Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES:
                 // TODO(b/289150947) : Update when feature is ready in service-appsearch.
+                // fall through
+            case Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES:
+                // TODO(b/296088047) : Update when feature is ready in service-appsearch.
                 return false;
             default:
                 return false;
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/GenericDocumentToPlatformConverter.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/GenericDocumentToPlatformConverter.java
index f87ab48..3775ab7 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/GenericDocumentToPlatformConverter.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/GenericDocumentToPlatformConverter.java
@@ -24,6 +24,8 @@
 import androidx.appsearch.app.GenericDocument;
 import androidx.core.util.Preconditions;
 
+import java.util.Arrays;
+
 /**
  * Translates between Platform and Jetpack versions of {@link GenericDocument}.
  *
@@ -113,6 +115,15 @@
                 .setCreationTimestampMillis(platformDocument.getCreationTimestampMillis());
         for (String propertyName : platformDocument.getPropertyNames()) {
             Object property = platformDocument.getProperty(propertyName);
+            if (propertyName.equals(GenericDocument.PARENT_TYPES_SYNTHETIC_PROPERTY)) {
+                if (!(property instanceof String[])) {
+                    throw new IllegalStateException(
+                            String.format("Parents list must be of String[] type, but got %s",
+                                    property.getClass().toString()));
+                }
+                jetpackBuilder.setParentTypes(Arrays.asList((String[]) property));
+                continue;
+            }
             if (property instanceof String[]) {
                 jetpackBuilder.setPropertyString(propertyName, (String[]) property);
             } else if (property instanceof long[]) {
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSpecToPlatformConverter.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSpecToPlatformConverter.java
index 4cb82c1..47bff5a 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSpecToPlatformConverter.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/converter/SearchSpecToPlatformConverter.java
@@ -127,6 +127,12 @@
             }
             ApiHelperForU.setJoinSpec(platformBuilder, jetpackSearchSpec.getJoinSpec());
         }
+
+        if (!jetpackSearchSpec.getFilterProperties().isEmpty()) {
+            // TODO(b/296088047): Remove this once property filters become available.
+            throw new UnsupportedOperationException(Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES
+                    + " is not available on this AppSearch implementation.");
+        }
         return platformBuilder.build();
     }
 
diff --git a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/FeaturesImpl.java b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/FeaturesImpl.java
index 2441e57d..a2a3e53 100644
--- a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/FeaturesImpl.java
+++ b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/FeaturesImpl.java
@@ -71,6 +71,34 @@
             case Features.LIST_FILTER_QUERY_LANGUAGE:
                 // TODO(b/208654892) : Update to reflect support in Android U+ once this feature is
                 //  synced over into service-appsearch.
+                // fall through
+            case Features.SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA:
+                // TODO(b/258715421) : Update to reflect support in Android U+ once this feature is
+                //  synced over into service-appsearch.
+                // fall through
+            case Features.SEARCH_SUGGESTION:
+                // TODO(b/227356108) : Update to reflect support in Android U+ once this feature is
+                //  synced over into service-appsearch.
+                // fall through
+            case Features.SCHEMA_SET_DELETION_PROPAGATION:
+                // TODO(b/268521214) : Update to reflect support in Android U+ once this feature is
+                //  synced over into service-appsearch.
+                // fall through
+            case Features.SET_SCHEMA_CIRCULAR_REFERENCES:
+                // TODO(b/280698121) : Update to reflect support in Android U+ once this feature is
+                //  synced over into service-appsearch.
+                // fall through
+            case Features.SCHEMA_ADD_PARENT_TYPE:
+                // TODO(b/269295094) : Update to reflect support in Android U+ once this feature is
+                //  synced over into service-appsearch.
+                // fall through
+            case Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES:
+                // TODO(b/289150947) : Update to reflect support in Android U+ once this feature is
+                //  synced over into service-appsearch.
+                // fall through
+            case Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES:
+                // TODO(b/296088047) : Update to reflect support in Android U+ once this feature is
+                //  synced over into service-appsearch.
                 return false;
             default:
                 return false; // AppSearch features in U+, absent in GMSCore AppSearch.
diff --git a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSpecToGmsConverter.java b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSpecToGmsConverter.java
index 8922bd4..1b7701b 100644
--- a/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSpecToGmsConverter.java
+++ b/appsearch/appsearch-play-services-storage/src/main/java/androidx/appsearch/playservicesstorage/converter/SearchSpecToGmsConverter.java
@@ -98,6 +98,12 @@
                     + "AppSearch implementation.");
         }
 
+        if (!jetpackSearchSpec.getFilterProperties().isEmpty()) {
+            // TODO(b/296088047): Remove this once property filters become available.
+            throw new UnsupportedOperationException(Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES
+                    + " is not available on this AppSearch implementation.");
+        }
+
         return gmsBuilder.build();
     }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
index af87d4d..690eb0f 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
@@ -45,8 +45,10 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 public abstract class AnnotationProcessorTestBase {
@@ -1324,6 +1326,8 @@
 
         Place place = Place.createPlace("id1", "namespace", 2000, "place_loc");
         GenericDocument placeGeneric = GenericDocument.fromDocumentClass(place);
+        placeGeneric = placeGeneric.toBuilder().setParentTypes(
+                Collections.singletonList("InterfaceRoot")).build();
         assertThat(placeGeneric.getId()).isEqualTo("id1");
         assertThat(placeGeneric.getNamespace()).isEqualTo("namespace");
         assertThat(placeGeneric.getCreationTimestampMillis()).isEqualTo(2000);
@@ -1337,6 +1341,8 @@
                 .setOrganizationDescription("organization_dec")
                 .build();
         GenericDocument organizationGeneric = GenericDocument.fromDocumentClass(organization);
+        organizationGeneric = organizationGeneric.toBuilder().setParentTypes(
+                Collections.singletonList("InterfaceRoot")).build();
         assertThat(organizationGeneric.getId()).isEqualTo("id2");
         assertThat(organizationGeneric.getNamespace()).isEqualTo("namespace");
         assertThat(organizationGeneric.getCreationTimestampMillis()).isEqualTo(3000);
@@ -1347,6 +1353,10 @@
         Business business = Business.createBusiness("id3", "namespace", 4000, "business_loc",
                 "business_dec", "business_name");
         GenericDocument businessGeneric = GenericDocument.fromDocumentClass(business);
+        // At runtime, business is type of BusinessImpl. As a result, the list of parent types
+        // for it should contain Business.
+        businessGeneric = businessGeneric.toBuilder().setParentTypes(new ArrayList<>(
+                Arrays.asList("Business", "Place", "Organization", "InterfaceRoot"))).build();
         assertThat(businessGeneric.getId()).isEqualTo("id3");
         assertThat(businessGeneric.getNamespace()).isEqualTo("namespace");
         assertThat(businessGeneric.getCreationTimestampMillis()).isEqualTo(4000);
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionInternalTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionInternalTestBase.java
index c2022c1..b835033 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionInternalTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AppSearchSessionInternalTestBase.java
@@ -29,6 +29,8 @@
 import androidx.appsearch.app.AppSearchSchema.PropertyConfig;
 import androidx.appsearch.app.AppSearchSchema.StringPropertyConfig;
 import androidx.appsearch.testutil.AppSearchEmail;
+import androidx.appsearch.util.DocumentIdUtil;
+import androidx.test.core.app.ApplicationProvider;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -37,6 +39,9 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -205,6 +210,592 @@
         assertThat(suggestions).containsExactly(resultOne, resultThree, resultFour);
     }
 
+    // TODO(b/296088047): move to CTS once the APIs it uses are public
+    @Test
+    public void testQuery_typePropertyFilters() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(
+                Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES));
+        // Schema registration
+        mDb1.setSchemaAsync(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(AppSearchEmail.SCHEMA)
+                        .build()).get();
+
+        // Index two documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("namespace", "id1")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("namespace", "id2")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example subject with some body")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(mDb1.putAsync(
+                new PutDocumentsRequest.Builder()
+                        .addGenericDocuments(email1, email2).build()));
+
+        // Query with type property filters {"Email", ["subject", "to"]}
+        SearchResults searchResults = mDb1.search("body", new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .addFilterProperties(AppSearchEmail.SCHEMA_TYPE, ImmutableList.of("subject", "to"))
+                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        // Only email2 should be returned because email1 doesn't have the term "body" in subject
+        // or to fields
+        assertThat(documents).containsExactly(email2);
+    }
+
+    // TODO(b/296088047): move to CTS once the APIs it uses are public
+    @Test
+    public void testQuery_typePropertyFiltersWithDifferentSchemaTypes() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(
+                Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES));
+        // Schema registration
+        mDb1.setSchemaAsync(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(AppSearchEmail.SCHEMA)
+                        .addSchemas(new AppSearchSchema.Builder("Note")
+                                .addProperty(new StringPropertyConfig.Builder("title")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                                        .setTokenizerType(
+                                                StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                                .addProperty(new StringPropertyConfig.Builder("body")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                                        .setTokenizerType(
+                                                StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                                .build())
+                        .build()).get();
+
+        // Index two documents
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("namespace", "id1")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        GenericDocument note =
+                new GenericDocument.Builder<>("namespace", "id2", "Note")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("title", "Note title")
+                        .setPropertyString("body", "Note body").build();
+        checkIsBatchResultSuccess(mDb1.putAsync(
+                new PutDocumentsRequest.Builder()
+                        .addGenericDocuments(email, note).build()));
+
+        // Query with type property paths {"Email": ["subject", "to"], "Note": ["body"]}. Note
+        // schema has body in its property filter but Email schema doesn't.
+        SearchResults searchResults = mDb1.search("body", new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .addFilterProperties(AppSearchEmail.SCHEMA_TYPE, ImmutableList.of("subject", "to"))
+                .addFilterProperties("Note", ImmutableList.of("body"))
+                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        // Only the note document should be returned because the email property filter doesn't
+        // allow searching in the body.
+        assertThat(documents).containsExactly(note);
+    }
+
+    // TODO(b/296088047): move to CTS once the APIs it uses are public
+    @Test
+    public void testQuery_typePropertyFiltersWithWildcard() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(
+                Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES));
+        // Schema registration
+        mDb1.setSchemaAsync(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(AppSearchEmail.SCHEMA)
+                        .addSchemas(new AppSearchSchema.Builder("Note")
+                                .addProperty(new StringPropertyConfig.Builder("title")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                                        .setTokenizerType(
+                                                StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                                .addProperty(new StringPropertyConfig.Builder("body")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                                        .setTokenizerType(
+                                                StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                                .build())
+                        .build()).get();
+
+        // Index two documents
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("namespace", "id1")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example subject with some body")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        GenericDocument note =
+                new GenericDocument.Builder<>("namespace", "id2", "Note")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("title", "Note title")
+                        .setPropertyString("body", "Note body").build();
+        checkIsBatchResultSuccess(mDb1.putAsync(
+                new PutDocumentsRequest.Builder()
+                        .addGenericDocuments(email, note).build()));
+
+        // Query with type property paths {"*": ["subject", "title"]}
+        SearchResults searchResults = mDb1.search("body", new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .addFilterProperties(SearchSpec.SCHEMA_TYPE_WILDCARD,
+                        ImmutableList.of("subject", "title"))
+                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        // The wildcard property filter will apply to both the Email and Note schema. The email
+        // document should be returned since it has the term "body" in its subject property. The
+        // note document should not be returned since it doesn't have the term "body" in the title
+        // property (subject property is not applicable for Note schema)
+        assertThat(documents).containsExactly(email);
+    }
+
+    // TODO(b/296088047): move to CTS once the APIs it uses are public
+    @Test
+    public void testQuery_typePropertyFiltersWithWildcardAndExplicitSchema() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(
+                Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES));
+        // Schema registration
+        mDb1.setSchemaAsync(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(AppSearchEmail.SCHEMA)
+                        .addSchemas(new AppSearchSchema.Builder("Note")
+                                .addProperty(new StringPropertyConfig.Builder("title")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                                        .setTokenizerType(
+                                                StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                                .addProperty(new StringPropertyConfig.Builder("body")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                                        .setTokenizerType(
+                                                StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                                .build())
+                        .build()).get();
+
+        // Index two documents
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("namespace", "id1")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example subject with some body")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        GenericDocument note =
+                new GenericDocument.Builder<>("namespace", "id2", "Note")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("title", "Note title")
+                        .setPropertyString("body", "Note body").build();
+        checkIsBatchResultSuccess(mDb1.putAsync(
+                new PutDocumentsRequest.Builder()
+                        .addGenericDocuments(email, note).build()));
+
+        // Query with type property paths {"*": ["subject", "title"], "Note": ["body"]}
+        SearchResults searchResults = mDb1.search("body", new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .addFilterProperties(SearchSpec.SCHEMA_TYPE_WILDCARD,
+                        ImmutableList.of("subject", "title"))
+                .addFilterProperties("Note", ImmutableList.of("body"))
+                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        // The wildcard property filter will only apply to the Email schema since Note schema has
+        // its own explicit property filter specified. The email document should be returned since
+        // it has the term "body" in its subject property. The note document should also be returned
+        // since it has the term "body" in the body property.
+        assertThat(documents).containsExactly(email, note);
+    }
+
+    // TODO(b/296088047): move to CTS once the APIs it uses are public
+    @Test
+    public void testQuery_typePropertyFiltersNonExistentType() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(
+                Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES));
+        // Schema registration
+        mDb1.setSchemaAsync(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(AppSearchEmail.SCHEMA)
+                        .addSchemas(new AppSearchSchema.Builder("Note")
+                                .addProperty(new StringPropertyConfig.Builder("title")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                                        .setTokenizerType(
+                                                StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                                .addProperty(new StringPropertyConfig.Builder("body")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                                        .setTokenizerType(
+                                                StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                                .build())
+                        .build()).get();
+
+        // Index two documents
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("namespace", "id1")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example subject with some body")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        GenericDocument note =
+                new GenericDocument.Builder<>("namespace", "id2", "Note")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("title", "Note title")
+                        .setPropertyString("body", "Note body").build();
+        checkIsBatchResultSuccess(mDb1.putAsync(
+                new PutDocumentsRequest.Builder()
+                        .addGenericDocuments(email, note).build()));
+
+        // Query with type property paths {"NonExistentType": ["to", "title"]}
+        SearchResults searchResults = mDb1.search("body", new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .addFilterProperties("NonExistentType", ImmutableList.of("to", "title"))
+                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        // The supplied property filters don't apply to either schema types. Both the documents
+        // should be returned since the term "body" is present in at least one of their properties.
+        assertThat(documents).containsExactly(email, note);
+    }
+
+    // TODO(b/296088047): move to CTS once the APIs it uses are public
+    @Test
+    public void testQuery_typePropertyFiltersEmpty() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(
+                Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES));
+        // Schema registration
+        mDb1.setSchemaAsync(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(AppSearchEmail.SCHEMA)
+                        .addSchemas(new AppSearchSchema.Builder("Note")
+                                .addProperty(new StringPropertyConfig.Builder("title")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                                        .setTokenizerType(
+                                                StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                                .addProperty(new StringPropertyConfig.Builder("body")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                                        .setTokenizerType(
+                                                StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                                .build())
+                        .build()).get();
+
+        // Index two documents
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("namespace", "id1")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        GenericDocument note =
+                new GenericDocument.Builder<>("namespace", "id2", "Note")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("title", "Note title")
+                        .setPropertyString("body", "Note body").build();
+        checkIsBatchResultSuccess(mDb1.putAsync(
+                new PutDocumentsRequest.Builder()
+                        .addGenericDocuments(email, note).build()));
+
+        // Query with type property paths {"email": []}
+        SearchResults searchResults = mDb1.search("body", new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .addFilterProperties(AppSearchEmail.SCHEMA_TYPE, Collections.emptyList())
+                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        // The email document should not be returned since the property filter doesn't allow
+        // searching any property.
+        assertThat(documents).containsExactly(note);
+    }
+
+    // TODO(b/296088047): move to CTS once the APIs it uses are public
+    @Test
+    public void testQueryWithJoin_typePropertyFiltersOnNestedSpec() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(
+                Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES));
+        assumeTrue(mDb1.getFeatures()
+                .isFeatureSupported(Features.JOIN_SPEC_AND_QUALIFIED_ID));
+
+        // A full example of how join might be used with property filters in join spec
+        AppSearchSchema actionSchema = new AppSearchSchema.Builder("ViewAction")
+                .addProperty(new StringPropertyConfig.Builder("entityId")
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                        .setJoinableValueType(StringPropertyConfig
+                                .JOINABLE_VALUE_TYPE_QUALIFIED_ID)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).addProperty(new StringPropertyConfig.Builder("note")
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).addProperty(new StringPropertyConfig.Builder("viewType")
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).build();
+
+        // Schema registration
+        mDb1.setSchemaAsync(
+                new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA, actionSchema)
+                        .build()).get();
+
+        // Index 2 email documents
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("namespace", "id1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+
+        AppSearchEmail inEmail2 =
+                new AppSearchEmail.Builder("namespace", "id2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+
+        // Index 2 viewAction documents, one for email1 and the other for email2
+        String qualifiedId1 =
+                DocumentIdUtil.createQualifiedId(
+                        ApplicationProvider.getApplicationContext().getPackageName(), DB_NAME_1,
+                        "namespace", "id1");
+        String qualifiedId2 =
+                DocumentIdUtil.createQualifiedId(
+                        ApplicationProvider.getApplicationContext().getPackageName(), DB_NAME_1,
+                        "namespace", "id2");
+        GenericDocument viewAction1 = new GenericDocument.Builder<>("NS", "id3", "ViewAction")
+                .setPropertyString("entityId", qualifiedId1)
+                .setPropertyString("note", "Viewed email on Monday")
+                .setPropertyString("viewType", "Stared").build();
+        GenericDocument viewAction2 = new GenericDocument.Builder<>("NS", "id4", "ViewAction")
+                .setPropertyString("entityId", qualifiedId2)
+                .setPropertyString("note", "Viewed email on Tuesday")
+                .setPropertyString("viewType", "Viewed").build();
+        checkIsBatchResultSuccess(mDb1.putAsync(
+                new PutDocumentsRequest.Builder().addGenericDocuments(inEmail, inEmail2,
+                                viewAction1, viewAction2)
+                        .build()));
+
+        // The nested search spec only allows searching the viewType property for viewAction
+        // schema type. It also specifies a property filter for Email schema.
+        SearchSpec nestedSearchSpec =
+                new SearchSpec.Builder()
+                        .addFilterProperties("ViewAction", ImmutableList.of("viewType"))
+                        .addFilterProperties(AppSearchEmail.SCHEMA_TYPE,
+                                ImmutableList.of("subject"))
+                        .build();
+
+        // Search for the term "Viewed" in join spec
+        JoinSpec js = new JoinSpec.Builder("entityId")
+                .setNestedSearch("Viewed", nestedSearchSpec)
+                .setAggregationScoringStrategy(JoinSpec.AGGREGATION_SCORING_RESULT_COUNT)
+                .build();
+
+        SearchResults searchResults = mDb1.search("body email", new SearchSpec.Builder()
+                .setRankingStrategy(SearchSpec.RANKING_STRATEGY_JOIN_AGGREGATE_SCORE)
+                .setJoinSpec(js)
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .build());
+
+        List<SearchResult> sr = searchResults.getNextPageAsync().get();
+
+        // Both email docs are returned, email2 comes first because it has higher number of
+        // joined documents. The property filters for Email schema specified in the nested search
+        // specs don't apply to the outer query (otherwise none of the email documents would have
+        // been returned).
+        assertThat(sr).hasSize(2);
+
+        // Email2 has a viewAction document viewAction2 that satisfies the property filters in
+        // the join spec, so it should be present in the joined results.
+        assertThat(sr.get(0).getGenericDocument().getId()).isEqualTo("id2");
+        assertThat(sr.get(0).getRankingSignal()).isEqualTo(1.0);
+        assertThat(sr.get(0).getJoinedResults()).hasSize(1);
+        assertThat(sr.get(0).getJoinedResults().get(0).getGenericDocument()).isEqualTo(viewAction2);
+
+        // Email1 has a viewAction document viewAction1 but it doesn't satisfy the property filters
+        // in the join spec, so it should not be present in the joined results.
+        assertThat(sr.get(1).getGenericDocument().getId()).isEqualTo("id1");
+        assertThat(sr.get(1).getRankingSignal()).isEqualTo(0.0);
+        assertThat(sr.get(1).getJoinedResults()).isEmpty();
+    }
+
+    // TODO(b/296088047): move to CTS once the APIs it uses are public
+    @Test
+    public void testQueryWithJoin_typePropertyFiltersOnOuterSpec() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(
+                Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES));
+        assumeTrue(mDb1.getFeatures()
+                .isFeatureSupported(Features.JOIN_SPEC_AND_QUALIFIED_ID));
+
+        // A full example of how join might be used with property filters in join spec
+        AppSearchSchema actionSchema = new AppSearchSchema.Builder("ViewAction")
+                .addProperty(new StringPropertyConfig.Builder("entityId")
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                        .setJoinableValueType(StringPropertyConfig
+                                .JOINABLE_VALUE_TYPE_QUALIFIED_ID)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).addProperty(new StringPropertyConfig.Builder("note")
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).addProperty(new StringPropertyConfig.Builder("viewType")
+                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                        .setIndexingType(StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)
+                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                        .build()
+                ).build();
+
+        // Schema registration
+        mDb1.setSchemaAsync(
+                new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA, actionSchema)
+                        .build()).get();
+
+        // Index 2 email documents
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("namespace", "id1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+
+        AppSearchEmail inEmail2 =
+                new AppSearchEmail.Builder("namespace", "id2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+
+        // Index 2 viewAction documents, one for email1 and the other for email2
+        String qualifiedId1 =
+                DocumentIdUtil.createQualifiedId(
+                        ApplicationProvider.getApplicationContext().getPackageName(), DB_NAME_1,
+                        "namespace", "id1");
+        String qualifiedId2 =
+                DocumentIdUtil.createQualifiedId(
+                        ApplicationProvider.getApplicationContext().getPackageName(), DB_NAME_1,
+                        "namespace", "id2");
+        GenericDocument viewAction1 = new GenericDocument.Builder<>("NS", "id3", "ViewAction")
+                .setPropertyString("entityId", qualifiedId1)
+                .setPropertyString("note", "Viewed email on Monday")
+                .setPropertyString("viewType", "Stared").build();
+        GenericDocument viewAction2 = new GenericDocument.Builder<>("NS", "id4", "ViewAction")
+                .setPropertyString("entityId", qualifiedId2)
+                .setPropertyString("note", "Viewed email on Tuesday")
+                .setPropertyString("viewType", "Viewed").build();
+        checkIsBatchResultSuccess(mDb1.putAsync(
+                new PutDocumentsRequest.Builder().addGenericDocuments(inEmail, inEmail2,
+                                viewAction1, viewAction2)
+                        .build()));
+
+        // The nested search spec doesn't specify any property filters.
+        SearchSpec nestedSearchSpec = new SearchSpec.Builder().build();
+
+        // Search for the term "Viewed" in join spec
+        JoinSpec js = new JoinSpec.Builder("entityId")
+                .setNestedSearch("Viewed", nestedSearchSpec)
+                .setAggregationScoringStrategy(JoinSpec.AGGREGATION_SCORING_RESULT_COUNT)
+                .build();
+
+        // Outer search spec adds property filters for both Email and ViewAction schema
+        SearchResults searchResults = mDb1.search("body email", new SearchSpec.Builder()
+                .setRankingStrategy(SearchSpec.RANKING_STRATEGY_JOIN_AGGREGATE_SCORE)
+                .setJoinSpec(js)
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .addFilterProperties(AppSearchEmail.SCHEMA_TYPE, ImmutableList.of("body"))
+                .addFilterProperties("ViewAction", ImmutableList.of("viewType"))
+                .build());
+
+        List<SearchResult> sr = searchResults.getNextPageAsync().get();
+
+        // Both email docs are returned as they both satisfy the property filters for Email, email2
+        // comes first because it has higher id lexicographically.
+        assertThat(sr).hasSize(2);
+
+        // Email2 has a viewAction document viewAction2 that satisfies the property filters in
+        // the outer spec (although those property filters are irrelevant for joined documents),
+        // it should be present in the joined results.
+        assertThat(sr.get(0).getGenericDocument().getId()).isEqualTo("id2");
+        assertThat(sr.get(0).getRankingSignal()).isEqualTo(1.0);
+        assertThat(sr.get(0).getJoinedResults()).hasSize(1);
+        assertThat(sr.get(0).getJoinedResults().get(0).getGenericDocument()).isEqualTo(viewAction2);
+
+        // Email1 has a viewAction document viewAction1 that doesn't satisfy the property filters
+        // in the outer spec, but property filters in the outer spec should not apply on joined
+        // documents, so viewAction1 should be present in the joined results.
+        assertThat(sr.get(1).getGenericDocument().getId()).isEqualTo("id1");
+        assertThat(sr.get(1).getRankingSignal()).isEqualTo(1.0);
+        assertThat(sr.get(0).getJoinedResults()).hasSize(1);
+        assertThat(sr.get(1).getJoinedResults().get(0).getGenericDocument()).isEqualTo(viewAction1);
+    }
+
+    // TODO(b/296088047): move to CTS once the APIs it uses are public
+    @Test
+    public void testQuery_typePropertyFiltersNotSupported() throws Exception {
+        assumeFalse(mDb1.getFeatures().isFeatureSupported(
+                Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES));
+        // Schema registration
+        mDb1.setSchemaAsync(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(AppSearchEmail.SCHEMA)
+                        .build()).get();
+
+        // Query with type property filters {"Email", ["subject", "to"]} and verify that unsupported
+        // exception is thrown
+        SearchSpec searchSpec = new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .addFilterProperties(AppSearchEmail.SCHEMA_TYPE, ImmutableList.of("subject", "to"))
+                .build();
+        UnsupportedOperationException exception =
+                assertThrows(UnsupportedOperationException.class,
+                        () -> mDb1.search("body", searchSpec));
+        assertThat(exception).hasMessageThat().contains(Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES
+                + " is not available on this AppSearch implementation.");
+    }
+
     // TODO(b/268521214): Move test to cts once deletion propagation is available in framework.
     @Test
     public void testGetSchema_joinableValueType() throws Exception {
@@ -704,4 +1295,371 @@
                                 + " is not available on this"
                                 + " AppSearch implementation.");
     }
+
+    @Test
+    public void testQuery_typeFilterWithPolymorphism() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+
+        // Schema registration
+        AppSearchSchema personSchema =
+                new AppSearchSchema.Builder("Person")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("name")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .build();
+        AppSearchSchema artistSchema =
+                new AppSearchSchema.Builder("Artist")
+                        .addParentType("Person")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("name")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .build();
+        mDb1.setSchemaAsync(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(personSchema)
+                                .addSchemas(artistSchema)
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .build())
+                .get();
+
+        // Index some documents
+        GenericDocument personDoc =
+                new GenericDocument.Builder<>("namespace", "id1", "Person")
+                        .setPropertyString("name", "Foo")
+                        .build();
+        GenericDocument artistDoc =
+                new GenericDocument.Builder<>("namespace", "id2", "Artist")
+                        .setPropertyString("name", "Foo")
+                        .build();
+        AppSearchEmail emailDoc =
+                new AppSearchEmail.Builder("namespace", "id3")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("Foo")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putAsync(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(personDoc, artistDoc, emailDoc)
+                                .build()));
+        GenericDocument artistDocWithParent = artistDoc.toBuilder().setParentTypes(
+                Collections.singletonList("Person")).build();
+
+        // Query for the documents
+        SearchResults searchResults =
+                mDb1.search(
+                        "Foo",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(3);
+        assertThat(documents).containsExactly(personDoc, artistDocWithParent, emailDoc);
+
+        // Query with a filter for the "Person" type should also include the "Artist" type.
+        searchResults =
+                mDb1.search(
+                        "Foo",
+                        new SearchSpec.Builder()
+                                .addFilterSchemas("Person")
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+        assertThat(documents).containsExactly(personDoc, artistDocWithParent);
+
+        // Query with a filters for the "Artist" type should not include the "Person" type.
+        searchResults =
+                mDb1.search(
+                        "Foo",
+                        new SearchSpec.Builder()
+                                .addFilterSchemas("Artist")
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents).containsExactly(artistDocWithParent);
+    }
+
+    @Test
+    public void testQuery_projectionWithPolymorphism() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+
+        // Schema registration
+        AppSearchSchema personSchema =
+                new AppSearchSchema.Builder("Person")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("name")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .addProperty(
+                                new StringPropertyConfig.Builder("emailAddress")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .build();
+        AppSearchSchema artistSchema =
+                new AppSearchSchema.Builder("Artist")
+                        .addParentType("Person")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("name")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .addProperty(
+                                new StringPropertyConfig.Builder("emailAddress")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .addProperty(
+                                new StringPropertyConfig.Builder("company")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .build();
+        mDb1.setSchemaAsync(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(personSchema)
+                                .addSchemas(artistSchema)
+                                .build())
+                .get();
+
+        // Index two documents
+        GenericDocument personDoc =
+                new GenericDocument.Builder<>("namespace", "id1", "Person")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("name", "Foo Person")
+                        .setPropertyString("emailAddress", "person@gmail.com")
+                        .build();
+        GenericDocument artistDoc =
+                new GenericDocument.Builder<>("namespace", "id2", "Artist")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("name", "Foo Artist")
+                        .setPropertyString("emailAddress", "artist@gmail.com")
+                        .setPropertyString("company", "Company")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putAsync(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(personDoc, artistDoc)
+                                .build()));
+
+        // Query with type property paths {"Person", ["name"]}, {"Artist", ["emailAddress"]}
+        // This will be expanded to paths {"Person", ["name"]}, {"Artist", ["name", "emailAddress"]}
+        // via polymorphism.
+        SearchResults searchResults =
+                mDb1.search(
+                        "Foo",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addProjection("Person", ImmutableList.of("name"))
+                                .addProjection("Artist", ImmutableList.of("emailAddress"))
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+        // The person document should have been returned with only the "name" property. The artist
+        // document should have been returned with all of its properties.
+        GenericDocument expectedPerson =
+                new GenericDocument.Builder<>("namespace", "id1", "Person")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("name", "Foo Person")
+                        .build();
+        GenericDocument expectedArtist =
+                new GenericDocument.Builder<>("namespace", "id2", "Artist")
+                        .setParentTypes(Collections.singletonList("Person"))
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("name", "Foo Artist")
+                        .setPropertyString("emailAddress", "artist@gmail.com")
+                        .build();
+        assertThat(documents).containsExactly(expectedPerson, expectedArtist);
+    }
+
+    @Test
+    public void testQuery_indexBasedOnParentTypePolymorphism() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+
+        // Schema registration
+        AppSearchSchema personSchema =
+                new AppSearchSchema.Builder("Person")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("name")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .build();
+        AppSearchSchema artistSchema =
+                new AppSearchSchema.Builder("Artist")
+                        .addParentType("Person")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("name")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .addProperty(
+                                new StringPropertyConfig.Builder("company")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .build();
+        AppSearchSchema messageSchema =
+                new AppSearchSchema.Builder("Message")
+                        .addProperty(
+                                new AppSearchSchema.DocumentPropertyConfig.Builder(
+                                        "sender", "Person")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
+                                        .setShouldIndexNestedProperties(true)
+                                        .build())
+                        .build();
+        mDb1.setSchemaAsync(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(personSchema)
+                                .addSchemas(artistSchema)
+                                .addSchemas(messageSchema)
+                                .build())
+                .get();
+
+        // Index some an artistDoc and a messageDoc
+        GenericDocument artistDoc =
+                new GenericDocument.Builder<>("namespace", "id1", "Artist")
+                        .setPropertyString("name", "Foo")
+                        .setPropertyString("company", "Bar")
+                        .build();
+        GenericDocument messageDoc =
+                new GenericDocument.Builder<>("namespace", "id2", "Message")
+                        // sender is defined as a Person, which accepts an Artist because Artist <:
+                        // Person.
+                        // However, indexing will be based on what's defined in Person, so the
+                        // "company"
+                        // property in artistDoc cannot be used to search this messageDoc.
+                        .setPropertyDocument("sender", artistDoc)
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putAsync(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(artistDoc, messageDoc)
+                                .build()));
+        GenericDocument expectedArtistDoc = artistDoc.toBuilder().setParentTypes(
+                Collections.singletonList("Person")).build();
+        GenericDocument expectedMessageDoc = messageDoc.toBuilder().setPropertyDocument("sender",
+                expectedArtistDoc).build();
+
+        // Query for the documents
+        SearchResults searchResults =
+                mDb1.search(
+                        "Foo",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+        assertThat(documents).containsExactly(expectedArtistDoc, expectedMessageDoc);
+
+        // The "company" property in artistDoc cannot be used to search messageDoc.
+        searchResults =
+                mDb1.search(
+                        "Bar",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents).containsExactly(expectedArtistDoc);
+    }
+
+    @Test
+    public void testQuery_parentTypeListIsTopologicalOrder() throws Exception {
+        assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
+        // Create the following subtype relation graph, where
+        // 1. A's direct parents are B and C.
+        // 2. B's direct parent is D.
+        // 3. C's direct parent is B and D.
+        // DFS order from A: [A, B, D, C]. Not acceptable because B and D appear before C.
+        // BFS order from A: [A, B, C, D]. Not acceptable because B appears before C.
+        // Topological order (all subtypes appear before supertypes) from A: [A, C, B, D].
+        AppSearchSchema schemaA =
+                new AppSearchSchema.Builder("A")
+                        .addParentType("B")
+                        .addParentType("C")
+                        .build();
+        AppSearchSchema schemaB =
+                new AppSearchSchema.Builder("B")
+                        .addParentType("D")
+                        .build();
+        AppSearchSchema schemaC =
+                new AppSearchSchema.Builder("C")
+                        .addParentType("B")
+                        .addParentType("D")
+                        .build();
+        AppSearchSchema schemaD =
+                new AppSearchSchema.Builder("D")
+                        .build();
+        mDb1.setSchemaAsync(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(schemaA)
+                                .addSchemas(schemaB)
+                                .addSchemas(schemaC)
+                                .addSchemas(schemaD)
+                                .build())
+                .get();
+
+        // Index some documents
+        GenericDocument docA =
+                new GenericDocument.Builder<>("namespace", "id1", "A")
+                        .build();
+        GenericDocument docB =
+                new GenericDocument.Builder<>("namespace", "id2", "B")
+                        .build();
+        GenericDocument docC =
+                new GenericDocument.Builder<>("namespace", "id3", "C")
+                        .build();
+        GenericDocument docD =
+                new GenericDocument.Builder<>("namespace", "id4", "D")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.putAsync(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(docA, docB, docC, docD)
+                                .build()));
+
+        GenericDocument expectedDocA =
+                docA.toBuilder().setParentTypes(
+                        new ArrayList<>(Arrays.asList("C", "B", "D"))).build();
+        GenericDocument expectedDocB =
+                docB.toBuilder().setParentTypes(
+                        Collections.singletonList("D")).build();
+        GenericDocument expectedDocC =
+                docC.toBuilder().setParentTypes(
+                        new ArrayList<>(Arrays.asList("B", "D"))).build();
+        // Query for the documents
+        SearchResults searchResults = mDb1.search("", new SearchSpec.Builder().build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(4);
+        assertThat(documents).containsExactly(expectedDocA, expectedDocB, expectedDocC, docD);
+    }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/GenericDocumentInternalTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/GenericDocumentInternalTest.java
index 8197731..8161c1c 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/GenericDocumentInternalTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/GenericDocumentInternalTest.java
@@ -18,13 +18,12 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertThrows;
-
 import android.os.Bundle;
 import android.os.Parcel;
 
 import org.junit.Test;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 
 /** Tests for private APIs of {@link GenericDocument}. */
@@ -68,46 +67,42 @@
     }
 
     @Test
-    public void testPropertyParcel_onePropertySet_success() {
-        String[] stringValues = {"a", "b"};
-        long[] longValues = {1L, 2L};
-        double[] doubleValues = {1.0, 2.0};
-        boolean[] booleanValues = {true, false};
-        byte[][] bytesValues = {new byte[1]};
-        Bundle[] bundleValues = {new Bundle()};
+    public void testRecreateFromParcelWithParentTypes() {
+        GenericDocument inDoc = new GenericDocument.Builder<>("namespace", "id1", "schema1")
+                .setParentTypes(new ArrayList<>(Arrays.asList("Class1", "Class2")))
+                .setScore(42)
+                .setPropertyString("propString", "Hello")
+                .setPropertyBytes("propBytes", new byte[][]{{1, 2}})
+                .setPropertyDocument(
+                        "propDocument",
+                        new GenericDocument.Builder<>("namespace", "id2", "schema2")
+                                .setPropertyString("propString", "Goodbye")
+                                .setPropertyBytes("propBytes", new byte[][]{{3, 4}})
+                                .build())
+                .build();
 
-        assertThat(new PropertyParcel.Builder("name").setStringValues(
-                stringValues).build().getStringValues()).isEqualTo(
-                Arrays.copyOf(stringValues, stringValues.length));
-        assertThat(new PropertyParcel.Builder("name").setLongValues(
-                longValues).build().getLongValues()).isEqualTo(
-                Arrays.copyOf(longValues, longValues.length));
-        assertThat(new PropertyParcel.Builder("name").setDoubleValues(
-                doubleValues).build().getDoubleValues()).isEqualTo(
-                Arrays.copyOf(doubleValues, doubleValues.length));
-        assertThat(new PropertyParcel.Builder("name").setBooleanValues(
-                booleanValues).build().getBooleanValues()).isEqualTo(
-                Arrays.copyOf(booleanValues, booleanValues.length));
-        assertThat(new PropertyParcel.Builder("name").setBytesValues(
-                bytesValues).build().getBytesValues()).isEqualTo(
-                Arrays.copyOf(bytesValues, bytesValues.length));
-        assertThat(new PropertyParcel.Builder("name").setDocumentValues(
-                bundleValues).build().getDocumentValues()).isEqualTo(
-                Arrays.copyOf(bundleValues, bundleValues.length));
-    }
+        // Serialize the document
+        Parcel inParcel = Parcel.obtain();
+        inParcel.writeBundle(inDoc.getBundle());
+        byte[] data = inParcel.marshall();
+        inParcel.recycle();
 
-    @Test
-    public void testPropertyParcel_moreThanOnePropertySet_exceptionThrown() {
-        String[] stringValues = {"a", "b"};
-        long[] longValues = {1L, 2L};
-        PropertyParcel.Builder propertyParcelBuilder =
-                new PropertyParcel.Builder("name")
-                        .setStringValues(stringValues)
-                        .setLongValues(longValues);
+        // Deserialize the document
+        Parcel outParcel = Parcel.obtain();
+        outParcel.unmarshall(data, 0, data.length);
+        outParcel.setDataPosition(0);
+        Bundle outBundle = outParcel.readBundle();
+        outParcel.recycle();
 
-        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
-                () -> propertyParcelBuilder.build());
-
-        assertThat(exception.getMessage()).contains("One and only one type array");
+        // Compare results
+        GenericDocument outDoc = new GenericDocument(outBundle);
+        assertThat(inDoc).isEqualTo(outDoc);
+        assertThat(outDoc.getParentTypes()).isEqualTo(Arrays.asList("Class1", "Class2"));
+        assertThat(outDoc.getPropertyString("propString")).isEqualTo("Hello");
+        assertThat(outDoc.getPropertyBytesArray("propBytes")).isEqualTo(new byte[][]{{1, 2}});
+        assertThat(outDoc.getPropertyDocument("propDocument").getPropertyString("propString"))
+                .isEqualTo("Goodbye");
+        assertThat(outDoc.getPropertyDocument("propDocument").getPropertyBytesArray("propBytes"))
+                .isEqualTo(new byte[][]{{3, 4}});
     }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecInternalTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecInternalTest.java
index 106c244..ed00e5a 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecInternalTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/SearchSpecInternalTest.java
@@ -18,10 +18,17 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
+
 import android.os.Bundle;
 
+import com.google.common.collect.ImmutableList;
+
 import org.junit.Test;
 
+import java.util.List;
+import java.util.Map;
+
 /** Tests for private APIs of {@link SearchSpec}. */
 public class SearchSpecInternalTest {
 
@@ -82,4 +89,38 @@
         assertThat(searchSpec3.getEnabledFeatures()).containsExactly(
                 Features.VERBATIM_SEARCH, Features.LIST_FILTER_QUERY_LANGUAGE);
     }
+
+    // TODO(b/296088047): move to CTS once the APIs it uses are public
+    @Test
+    public void testGetPropertyFiltersTypePropertyMasks() {
+        SearchSpec searchSpec = new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                .addFilterProperties("TypeA", ImmutableList.of("field1", "field2.subfield2"))
+                .addFilterProperties("TypeB", ImmutableList.of("field7"))
+                .addFilterProperties("TypeC", ImmutableList.of())
+                .build();
+
+        Map<String, List<String>> typePropertyPathMap = searchSpec.getFilterProperties();
+        assertThat(typePropertyPathMap.keySet())
+                .containsExactly("TypeA", "TypeB", "TypeC");
+        assertThat(typePropertyPathMap.get("TypeA")).containsExactly("field1", "field2.subfield2");
+        assertThat(typePropertyPathMap.get("TypeB")).containsExactly("field7");
+        assertThat(typePropertyPathMap.get("TypeC")).isEmpty();
+    }
+
+    // TODO(b/296088047): move to CTS once the APIs it uses are public
+    @Test
+    public void testBuilder_throwsException_whenTypePropertyFilterNotInSchemaFilter() {
+        SearchSpec.Builder searchSpecBuilder = new SearchSpec.Builder()
+                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                .addFilterSchemas("Schema1", "Schema2")
+                .addFilterPropertyPaths("Schema3", ImmutableList.of(
+                        new PropertyPath("field1"), new PropertyPath("field2.subfield2")));
+
+        IllegalStateException exception =
+                assertThrows(IllegalStateException.class, searchSpecBuilder::build);
+        assertThat(exception.getMessage())
+                .isEqualTo("The schema: Schema3 exists in the property filter but doesn't"
+                        + " exist in the schema filter.");
+    }
 }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/safeparcel/GenericDocumentParcelTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/safeparcel/GenericDocumentParcelTest.java
new file mode 100644
index 0000000..47df245
--- /dev/null
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/safeparcel/GenericDocumentParcelTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appsearch.app.safeparcel;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/** Tests for {@link androidx.appsearch.app.GenericDocument} related SafeParcels. */
+public class GenericDocumentParcelTest {
+    @Test
+    public void testPropertyParcel_onePropertySet_success() {
+        String[] stringValues = {"a", "b"};
+        long[] longValues = {1L, 2L};
+        double[] doubleValues = {1.0, 2.0};
+        boolean[] booleanValues = {true, false};
+        byte[][] bytesValues = {new byte[1]};
+        GenericDocumentParcel[] docValues = {(new GenericDocumentParcel.Builder(
+                "namespace", "id", "schemaType")).build()};
+
+        assertThat(new PropertyParcel.Builder("name").setStringValues(
+                stringValues).build().getStringValues()).isEqualTo(
+                Arrays.copyOf(stringValues, stringValues.length));
+        assertThat(new PropertyParcel.Builder("name").setLongValues(
+                longValues).build().getLongValues()).isEqualTo(
+                Arrays.copyOf(longValues, longValues.length));
+        assertThat(new PropertyParcel.Builder("name").setDoubleValues(
+                doubleValues).build().getDoubleValues()).isEqualTo(
+                Arrays.copyOf(doubleValues, doubleValues.length));
+        assertThat(new PropertyParcel.Builder("name").setBooleanValues(
+                booleanValues).build().getBooleanValues()).isEqualTo(
+                Arrays.copyOf(booleanValues, booleanValues.length));
+        assertThat(new PropertyParcel.Builder("name").setBytesValues(
+                bytesValues).build().getBytesValues()).isEqualTo(
+                Arrays.copyOf(bytesValues, bytesValues.length));
+        assertThat(new PropertyParcel.Builder("name").setDocumentValues(
+                docValues).build().getDocumentValues()).isEqualTo(
+                Arrays.copyOf(docValues, docValues.length));
+    }
+
+    @Test
+    public void testPropertyParcel_moreThanOnePropertySet_exceptionThrown() {
+        String[] stringValues = {"a", "b"};
+        long[] longValues = {1L, 2L};
+        PropertyParcel.Builder propertyParcelBuilder =
+                new PropertyParcel.Builder("name")
+                        .setStringValues(stringValues)
+                        .setLongValues(longValues);
+
+        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+                () -> propertyParcelBuilder.build());
+
+        assertThat(exception.getMessage()).contains("One and only one type array");
+    }
+
+    @Test
+    public void testGenericDocumentParcel_propertiesGeneratedCorrectly() {
+        GenericDocumentParcel.Builder builder =
+                new GenericDocumentParcel.Builder(
+                        /*namespace=*/ "namespace",
+                        /*id=*/ "id",
+                        /*schemaType=*/ "schemaType");
+        long[] longArray = new long[]{1L, 2L, 3L};
+        String[] stringArray = new String[]{"hello", "world", "!"};
+        builder.putInPropertyMap(/*name=*/ "longArray", /*values=*/ longArray);
+        builder.putInPropertyMap(/*name=*/ "stringArray", /*values=*/ stringArray);
+        GenericDocumentParcel genericDocumentParcel = builder.build();
+
+        PropertyParcel[] properties = genericDocumentParcel.getProperties();
+        Map<String, PropertyParcel> propertyMap = genericDocumentParcel.getPropertyMap();
+        PropertyParcel longArrayProperty = new PropertyParcel.Builder(
+                /*name=*/ "longArray").setLongValues(longArray).build();
+        PropertyParcel stringArrayProperty = new PropertyParcel.Builder(
+                /*name=*/ "stringArray").setStringValues(stringArray).build();
+
+        assertThat(properties).asList().containsExactly(longArrayProperty, stringArrayProperty);
+        assertThat(propertyMap).containsExactly("longArray", longArrayProperty,
+                "stringArray", stringArrayProperty);
+    }
+
+    @Test
+    public void testGenericDocumentParcel_buildFromAnotherDocumentParcelCorrectly() {
+        GenericDocumentParcel.Builder builder =
+                new GenericDocumentParcel.Builder(
+                        /*namespace=*/ "namespace",
+                        /*id=*/ "id",
+                        /*schemaType=*/ "schemaType");
+        long[] longArray = new long[]{1L, 2L, 3L};
+        String[] stringArray = new String[]{"hello", "world", "!"};
+        builder.putInPropertyMap(/*name=*/ "longArray", /*values=*/ longArray);
+        builder.putInPropertyMap(/*name=*/ "stringArray", /*values=*/ stringArray);
+        GenericDocumentParcel genericDocumentParcel = builder.build();
+
+        GenericDocumentParcel genericDocumentParcelCopy =
+                new GenericDocumentParcel.Builder(genericDocumentParcel).build();
+
+        assertThat(genericDocumentParcelCopy.getNamespace()).isEqualTo(
+                genericDocumentParcel.getNamespace());
+        assertThat(genericDocumentParcelCopy.getId()).isEqualTo(genericDocumentParcel.getId());
+        assertThat(genericDocumentParcelCopy.getSchemaType()).isEqualTo(
+                genericDocumentParcel.getSchemaType());
+        assertThat(genericDocumentParcelCopy.getCreationTimestampMillis()).isEqualTo(
+                genericDocumentParcel.getCreationTimestampMillis());
+        assertThat(genericDocumentParcelCopy.getTtlMillis()).isEqualTo(
+                genericDocumentParcel.getTtlMillis());
+        assertThat(genericDocumentParcelCopy.getScore()).isEqualTo(
+                genericDocumentParcel.getScore());
+        // Check it is a copy.
+        assertThat(genericDocumentParcelCopy).isNotSameInstanceAs(genericDocumentParcel);
+        assertThat(genericDocumentParcelCopy.getProperties()).isEqualTo(
+                genericDocumentParcel.getProperties());
+    }
+}
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSchemaCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSchemaCtsTest.java
index 84661ff..4118c45 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSchemaCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSchemaCtsTest.java
@@ -327,7 +327,6 @@
                 + "    {\n"
                 + "      name: \"document\",\n"
                 + "      shouldIndexNestedProperties: true,\n"
-                + "      indexableNestedProperties: [],\n"
                 + "      schemaType: \"builtin:Email\",\n"
                 + "      cardinality: CARDINALITY_REPEATED,\n"
                 + "      dataType: DATA_TYPE_DOCUMENT,\n"
@@ -408,7 +407,10 @@
                 + "  ]\n"
                 + "}";
 
-        assertThat(schemaString).isEqualTo(expectedString);
+        String[] lines = expectedString.split("\n");
+        for (String line : lines) {
+            assertThat(schemaString).contains(line);
+        }
     }
 
     @Test
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
index 7bde81f..ccfaa65 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/AppSearchSessionCtsTestBase.java
@@ -5761,295 +5761,6 @@
     }
 
     @Test
-    public void testQuery_typeFilterWithPolymorphism() throws Exception {
-        assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
-
-        // Schema registration
-        AppSearchSchema personSchema =
-                new AppSearchSchema.Builder("Person")
-                        .addProperty(
-                                new StringPropertyConfig.Builder("name")
-                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
-                                        .setIndexingType(
-                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
-                                        .build())
-                        .build();
-        AppSearchSchema artistSchema =
-                new AppSearchSchema.Builder("Artist")
-                        .addParentType("Person")
-                        .addProperty(
-                                new StringPropertyConfig.Builder("name")
-                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
-                                        .setIndexingType(
-                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
-                                        .build())
-                        .build();
-        mDb1.setSchemaAsync(
-                        new SetSchemaRequest.Builder()
-                                .addSchemas(personSchema)
-                                .addSchemas(artistSchema)
-                                .addSchemas(AppSearchEmail.SCHEMA)
-                                .build())
-                .get();
-
-        // Index some documents
-        GenericDocument personDoc =
-                new GenericDocument.Builder<>("namespace", "id1", "Person")
-                        .setPropertyString("name", "Foo")
-                        .build();
-        GenericDocument artistDoc =
-                new GenericDocument.Builder<>("namespace", "id2", "Artist")
-                        .setPropertyString("name", "Foo")
-                        .build();
-        AppSearchEmail emailDoc =
-                new AppSearchEmail.Builder("namespace", "id3")
-                        .setFrom("from@example.com")
-                        .setTo("to1@example.com", "to2@example.com")
-                        .setSubject("testPut example")
-                        .setBody("Foo")
-                        .build();
-        checkIsBatchResultSuccess(
-                mDb1.putAsync(
-                        new PutDocumentsRequest.Builder()
-                                .addGenericDocuments(personDoc, artistDoc, emailDoc)
-                                .build()));
-
-        // Query for the documents
-        SearchResults searchResults =
-                mDb1.search(
-                        "Foo",
-                        new SearchSpec.Builder()
-                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
-                                .build());
-        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
-        assertThat(documents).hasSize(3);
-        assertThat(documents).containsExactly(personDoc, artistDoc, emailDoc);
-
-        // Query with a filter for the "Person" type should also include the "Artist" type.
-        searchResults =
-                mDb1.search(
-                        "Foo",
-                        new SearchSpec.Builder()
-                                .addFilterSchemas("Person")
-                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
-                                .build());
-        documents = convertSearchResultsToDocuments(searchResults);
-        assertThat(documents).hasSize(2);
-        assertThat(documents).containsExactly(personDoc, artistDoc);
-
-        // Query with a filters for the "Artist" type should not include the "Person" type.
-        searchResults =
-                mDb1.search(
-                        "Foo",
-                        new SearchSpec.Builder()
-                                .addFilterSchemas("Artist")
-                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
-                                .build());
-        documents = convertSearchResultsToDocuments(searchResults);
-        assertThat(documents).hasSize(1);
-        assertThat(documents).containsExactly(artistDoc);
-    }
-
-    @Test
-    public void testQuery_projectionWithPolymorphism() throws Exception {
-        assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
-
-        // Schema registration
-        AppSearchSchema personSchema =
-                new AppSearchSchema.Builder("Person")
-                        .addProperty(
-                                new StringPropertyConfig.Builder("name")
-                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
-                                        .setIndexingType(
-                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
-                                        .build())
-                        .addProperty(
-                                new StringPropertyConfig.Builder("emailAddress")
-                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
-                                        .setIndexingType(
-                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
-                                        .build())
-                        .build();
-        AppSearchSchema artistSchema =
-                new AppSearchSchema.Builder("Artist")
-                        .addParentType("Person")
-                        .addProperty(
-                                new StringPropertyConfig.Builder("name")
-                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
-                                        .setIndexingType(
-                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
-                                        .build())
-                        .addProperty(
-                                new StringPropertyConfig.Builder("emailAddress")
-                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
-                                        .setIndexingType(
-                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
-                                        .build())
-                        .addProperty(
-                                new StringPropertyConfig.Builder("company")
-                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
-                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
-                                        .setIndexingType(
-                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
-                                        .build())
-                        .build();
-        mDb1.setSchemaAsync(
-                        new SetSchemaRequest.Builder()
-                                .addSchemas(personSchema)
-                                .addSchemas(artistSchema)
-                                .build())
-                .get();
-
-        // Index two documents
-        GenericDocument personDoc =
-                new GenericDocument.Builder<>("namespace", "id1", "Person")
-                        .setCreationTimestampMillis(1000)
-                        .setPropertyString("name", "Foo Person")
-                        .setPropertyString("emailAddress", "person@gmail.com")
-                        .build();
-        GenericDocument artistDoc =
-                new GenericDocument.Builder<>("namespace", "id2", "Artist")
-                        .setCreationTimestampMillis(1000)
-                        .setPropertyString("name", "Foo Artist")
-                        .setPropertyString("emailAddress", "artist@gmail.com")
-                        .setPropertyString("company", "Company")
-                        .build();
-        checkIsBatchResultSuccess(
-                mDb1.putAsync(
-                        new PutDocumentsRequest.Builder()
-                                .addGenericDocuments(personDoc, artistDoc)
-                                .build()));
-
-        // Query with type property paths {"Person", ["name"]}, {"Artist", ["emailAddress"]}
-        // This will be expanded to paths {"Person", ["name"]}, {"Artist", ["name", "emailAddress"]}
-        // via polymorphism.
-        SearchResults searchResults =
-                mDb1.search(
-                        "Foo",
-                        new SearchSpec.Builder()
-                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
-                                .addProjection("Person", ImmutableList.of("name"))
-                                .addProjection("Artist", ImmutableList.of("emailAddress"))
-                                .build());
-        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
-
-        // The person document should have been returned with only the "name" property. The artist
-        // document should have been returned with all of its properties.
-        GenericDocument expectedPerson =
-                new GenericDocument.Builder<>("namespace", "id1", "Person")
-                        .setCreationTimestampMillis(1000)
-                        .setPropertyString("name", "Foo Person")
-                        .build();
-        GenericDocument expectedArtist =
-                new GenericDocument.Builder<>("namespace", "id2", "Artist")
-                        .setCreationTimestampMillis(1000)
-                        .setPropertyString("name", "Foo Artist")
-                        .setPropertyString("emailAddress", "artist@gmail.com")
-                        .build();
-        assertThat(documents).containsExactly(expectedPerson, expectedArtist);
-    }
-
-    @Test
-    public void testQuery_indexBasedOnParentTypePolymorphism() throws Exception {
-        assumeTrue(mDb1.getFeatures().isFeatureSupported(Features.SCHEMA_ADD_PARENT_TYPE));
-
-        // Schema registration
-        AppSearchSchema personSchema =
-                new AppSearchSchema.Builder("Person")
-                        .addProperty(
-                                new StringPropertyConfig.Builder("name")
-                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
-                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
-                                        .setIndexingType(
-                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
-                                        .build())
-                        .build();
-        AppSearchSchema artistSchema =
-                new AppSearchSchema.Builder("Artist")
-                        .addParentType("Person")
-                        .addProperty(
-                                new StringPropertyConfig.Builder("name")
-                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
-                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
-                                        .setIndexingType(
-                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
-                                        .build())
-                        .addProperty(
-                                new StringPropertyConfig.Builder("company")
-                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
-                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
-                                        .setIndexingType(
-                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
-                                        .build())
-                        .build();
-        AppSearchSchema messageSchema =
-                new AppSearchSchema.Builder("Message")
-                        .addProperty(
-                                new AppSearchSchema.DocumentPropertyConfig.Builder(
-                                        "sender", "Person")
-                                        .setCardinality(PropertyConfig.CARDINALITY_REQUIRED)
-                                        .setShouldIndexNestedProperties(true)
-                                        .build())
-                        .build();
-        mDb1.setSchemaAsync(
-                        new SetSchemaRequest.Builder()
-                                .addSchemas(personSchema)
-                                .addSchemas(artistSchema)
-                                .addSchemas(messageSchema)
-                                .build())
-                .get();
-
-        // Index some an artistDoc and a messageDoc
-        GenericDocument artistDoc =
-                new GenericDocument.Builder<>("namespace", "id1", "Artist")
-                        .setPropertyString("name", "Foo")
-                        .setPropertyString("company", "Bar")
-                        .build();
-        GenericDocument messageDoc =
-                new GenericDocument.Builder<>("namespace", "id2", "Message")
-                        // sender is defined as a Person, which accepts an Artist because Artist <:
-                        // Person.
-                        // However, indexing will be based on what's defined in Person, so the
-                        // "company"
-                        // property in artistDoc cannot be used to search this messageDoc.
-                        .setPropertyDocument("sender", artistDoc)
-                        .build();
-        checkIsBatchResultSuccess(
-                mDb1.putAsync(
-                        new PutDocumentsRequest.Builder()
-                                .addGenericDocuments(artistDoc, messageDoc)
-                                .build()));
-
-        // Query for the documents
-        SearchResults searchResults =
-                mDb1.search(
-                        "Foo",
-                        new SearchSpec.Builder()
-                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
-                                .build());
-        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
-        assertThat(documents).hasSize(2);
-        assertThat(documents).containsExactly(artistDoc, messageDoc);
-
-        // The "company" property in artistDoc cannot be used to search messageDoc.
-        searchResults =
-                mDb1.search(
-                        "Bar",
-                        new SearchSpec.Builder()
-                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
-                                .build());
-        documents = convertSearchResultsToDocuments(searchResults);
-        assertThat(documents).hasSize(1);
-        assertThat(documents).containsExactly(artistDoc);
-    }
-
-    @Test
     public void testSetSchema_indexableNestedPropsList() throws Exception {
         assumeTrue(
                 mDb1.getFeatures()
@@ -6704,4 +6415,42 @@
         assertThat(outDocuments).hasSize(1);
         assertThat(outDocuments).containsExactly(org2);
     }
+
+    @Test
+    public void testSetSchema_toString_containsIndexableNestedPropsList() throws Exception {
+        assumeTrue(
+                mDb1.getFeatures()
+                        .isFeatureSupported(Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES));
+
+        AppSearchSchema emailSchema =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("subject")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .addProperty(
+                                new AppSearchSchema.DocumentPropertyConfig.Builder(
+                                        "sender", "Person")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+                                        .setShouldIndexNestedProperties(false)
+                                        .addIndexableNestedProperties(
+                                                Arrays.asList(
+                                                        "name", "worksFor.name", "worksFor.notes"))
+                                        .build())
+                        .addProperty(
+                                new AppSearchSchema.DocumentPropertyConfig.Builder(
+                                        "recipient", "Person")
+                                        .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+                                        .setShouldIndexNestedProperties(true)
+                                        .build())
+                        .build();
+        String expectedIndexableNestedPropertyMessage =
+                "indexableNestedProperties: [name, worksFor.notes, worksFor.name]";
+
+        assertThat(emailSchema.toString()).contains(expectedIndexableNestedPropertyMessage);
+
+    }
 }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
index 6a198be..012c789 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
@@ -16,6 +16,7 @@
 package androidx.appsearch.app;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
 
 /**
  * A class that encapsulates all features that are only supported in certain cases (e.g. only on
@@ -110,13 +111,22 @@
      */
     String SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA = "SEARCH_SPEC_GROUPING_TYPE_PER_SCHEMA";
 
-    /** Feature for {@link #isFeatureSupported(String)}. This feature covers
+    /**
+     * Feature for {@link #isFeatureSupported(String)}. This feature covers
      * {@link SearchSpec.Builder#setPropertyWeights}.
      */
     String SEARCH_SPEC_PROPERTY_WEIGHTS = "SEARCH_SPEC_PROPERTY_WEIGHTS";
 
     /**
      * Feature for {@link #isFeatureSupported(String)}. This feature covers
+     * {@link SearchSpec.Builder#addFilterProperties}.
+     * @exportToFramework:hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    String SEARCH_SPEC_ADD_FILTER_PROPERTIES = "SEARCH_SPEC_ADD_FILTER_PROPERTIES";
+
+    /**
+     * Feature for {@link #isFeatureSupported(String)}. This feature covers
      * {@link SearchSpec.Builder#setRankingStrategy(String)}.
      */
     String SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION = "SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION";
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
index 6dcecce..cdb3004 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
@@ -77,6 +77,10 @@
     private static final String TTL_MILLIS_FIELD = "ttlMillis";
     private static final String CREATION_TIMESTAMP_MILLIS_FIELD = "creationTimestampMillis";
     private static final String NAMESPACE_FIELD = "namespace";
+    private static final String PARENT_TYPES_FIELD = "parentTypes";
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final String PARENT_TYPES_SYNTHETIC_PROPERTY = "$$__AppSearch__parentTypes";
 
     /**
      * The maximum number of indexed properties a document can have.
@@ -190,6 +194,22 @@
     }
 
     /**
+     * Returns the list of parent types of the {@link GenericDocument}'s type.
+     *
+     * <p>It is guaranteed that child types appear before parent types in the list.
+     * <!--@exportToFramework:hide-->
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @Nullable
+    public List<String> getParentTypes() {
+        List<String> result = mBundle.getStringArrayList(PARENT_TYPES_FIELD);
+        if (result == null) {
+            return null;
+        }
+        return Collections.unmodifiableList(result);
+    }
+
+    /**
      * Returns the creation timestamp of the {@link GenericDocument}, in milliseconds.
      *
      * <p>The value is in the {@link System#currentTimeMillis} time base.
@@ -979,6 +999,10 @@
         builder.append("id: \"").append(getId()).append("\",\n");
         builder.append("score: ").append(getScore()).append(",\n");
         builder.append("schemaType: \"").append(getSchemaType()).append("\",\n");
+        List<String> parentTypes = getParentTypes();
+        if (parentTypes != null) {
+            builder.append("parentTypes: ").append(parentTypes).append("\n");
+        }
         builder
                 .append("creationTimestampMillis: ")
                 .append(getCreationTimestampMillis())
@@ -1170,6 +1194,23 @@
         }
 
         /**
+         * Sets the list of parent types of the {@link GenericDocument}'s type.
+         *
+         * <p>Child types must appear before parent types in the list.
+         * <!--@exportToFramework:hide-->
+         */
+        @CanIgnoreReturnValue
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        @NonNull
+        public BuilderType setParentTypes(@NonNull List<String> parentTypes) {
+            Preconditions.checkNotNull(parentTypes);
+            resetIfBuilt();
+            mBundle.putStringArrayList(GenericDocument.PARENT_TYPES_FIELD,
+                    new ArrayList<>(parentTypes));
+            return mBuilderTypeInstance;
+        }
+
+        /**
          * Sets the score of the {@link GenericDocument}.
          *
          * <p>The score is a query-independent measure of the document's quality, relative to
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 a8ccaf9..f9d87c3 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
@@ -55,9 +55,19 @@
      */
     public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
 
+    /**
+     * Schema type to be used in {@link SearchSpec.Builder#addFilterProperties(String, Collection)}
+     * to apply property paths to all results, excepting any types that have had their own, specific
+     * property paths set.
+     * @exportToFramework:hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final String SCHEMA_TYPE_WILDCARD = "*";
+
     static final String TERM_MATCH_TYPE_FIELD = "termMatchType";
     static final String SCHEMA_FIELD = "schema";
     static final String NAMESPACE_FIELD = "namespace";
+    static final String PROPERTY_FIELD = "property";
     static final String PACKAGE_NAME_FIELD = "packageName";
     static final String NUM_PER_PAGE_FIELD = "numPerPage";
     static final String RANKING_STRATEGY_FIELD = "rankingStrategy";
@@ -268,6 +278,30 @@
     }
 
     /**
+     * Returns the map of schema and target properties to search over.
+     *
+     * <p>If empty, will search over all schema and properties.
+     *
+     * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned
+     * by this function, rather than calling it multiple times.
+     *
+     * @exportToFramework:hide
+     */
+    @NonNull
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public Map<String, List<String>> getFilterProperties() {
+        Bundle typePropertyPathsBundle = Preconditions.checkNotNull(
+                mBundle.getBundle(PROPERTY_FIELD));
+        Set<String> schemas = typePropertyPathsBundle.keySet();
+        Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemas.size());
+        for (String schema : schemas) {
+            typePropertyPathsMap.put(schema, Preconditions.checkNotNull(
+                    typePropertyPathsBundle.getStringArrayList(schema)));
+        }
+        return typePropertyPathsMap;
+    }
+
+    /**
      * Returns the list of namespaces to search over.
      *
      * <p>If empty, the query will search over all namespaces.
@@ -512,6 +546,7 @@
     public static final class Builder {
         private ArrayList<String> mSchemas = new ArrayList<>();
         private ArrayList<String> mNamespaces = new ArrayList<>();
+        private Bundle mTypePropertyFilters = new Bundle();
         private ArrayList<String> mPackageNames = new ArrayList<>();
         private ArraySet<String> mEnabledFeatures = new ArraySet<>();
         private Bundle mProjectionTypePropertyMasks = new Bundle();
@@ -629,6 +664,145 @@
 // @exportToFramework:endStrip()
 
         /**
+         * Adds property paths for the specified type to the property filter of
+         * {@link SearchSpec} Entry. Only returns documents that have matches under
+         * the specified properties. If property paths are added for a type, then only the
+         * properties referred to will be searched for results of that type.
+         *
+         * <p> If a property path that is specified isn't present in a result, it will be ignored
+         * for that result. Property paths cannot be null.
+         *
+         * <p>If no property paths are added for a particular type, then all properties of
+         * results of that type will be searched.
+         *
+         * <p>Example properties: 'body', 'sender.name', 'sender.emailaddress', etc.
+         *
+         * <p>If property paths are added for the
+         * {@link SearchSpec#SCHEMA_TYPE_WILDCARD}, then those property paths will
+         * apply to all results, excepting any types that have their own, specific property paths
+         * set.
+         *
+         * @param schema the {@link AppSearchSchema} that contains the target properties
+         * @param propertyPaths The String version of {@link PropertyPath}. A dot-delimited
+         *                      sequence of property names.
+         *
+         * @exportToFramework:hide
+         */
+         // TODO(b/296088047) unhide from framework when type property filters are made public.
+        @NonNull
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        // @exportToFramework:startStrip()
+        @RequiresFeature(
+                enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+                name = Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES)
+        // @exportToFramework:endStrip()
+        public Builder addFilterProperties(@NonNull String schema,
+                @NonNull Collection<String> propertyPaths) {
+            Preconditions.checkNotNull(schema);
+            Preconditions.checkNotNull(propertyPaths);
+            resetIfBuilt();
+            ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
+            for (String propertyPath : propertyPaths) {
+                Preconditions.checkNotNull(propertyPath);
+                propertyPathsArrayList.add(propertyPath);
+            }
+            mTypePropertyFilters.putStringArrayList(schema, propertyPathsArrayList);
+            return this;
+        }
+
+        /**
+         * Adds property paths for the specified type to the property filter of
+         * {@link SearchSpec} Entry. Only returns documents that have matches under the specified
+         * properties. If property paths are added for a type, then only the properties referred
+         * to will be searched for results of that type.
+         *
+         * @see #addFilterProperties(String, Collection)
+         *
+         * @param schema the {@link AppSearchSchema} that contains the target properties
+         * @param propertyPaths The {@link PropertyPath} to search search over
+         *
+         * @exportToFramework:hide
+         */
+         // TODO(b/296088047) unhide from framework when type property filters are made public.
+        @NonNull
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        // @exportToFramework:startStrip()
+        @RequiresFeature(
+                enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+                name = Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES)
+        // @exportToFramework:endStrip()
+        public Builder addFilterPropertyPaths(@NonNull String schema,
+                @NonNull Collection<PropertyPath> propertyPaths) {
+            Preconditions.checkNotNull(schema);
+            Preconditions.checkNotNull(propertyPaths);
+            ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
+            for (PropertyPath propertyPath : propertyPaths) {
+                propertyPathsArrayList.add(propertyPath.toString());
+            }
+            return addFilterProperties(schema, propertyPathsArrayList);
+        }
+
+
+// @exportToFramework:startStrip()
+        /**
+         * Adds property paths for the specified type to the property filter of
+         * {@link SearchSpec} Entry. Only returns documents that have matches under the specified
+         * properties. If property paths are added for a type, then only the properties referred
+         * to will be searched for results of that type.
+         *
+         * @see #addFilterProperties(String, Collection)
+         *
+         * @param documentClass class annotated with {@link Document}.
+         * @param propertyPaths The String version of {@link PropertyPath}. A dot-delimited
+                                sequence of property names.
+         *
+         */
+        @NonNull
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        @RequiresFeature(
+                enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+                name = Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES)
+        public Builder addFilterProperties(@NonNull Class<?> documentClass,
+                @NonNull Collection<String> propertyPaths) throws AppSearchException {
+            Preconditions.checkNotNull(documentClass);
+            Preconditions.checkNotNull(propertyPaths);
+            resetIfBuilt();
+            DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
+            DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
+            return addFilterProperties(factory.getSchemaName(), propertyPaths);
+        }
+// @exportToFramework:endStrip()
+
+// @exportToFramework:startStrip()
+        /**
+         * Adds property paths for the specified type to the property filter of
+         * {@link SearchSpec} Entry. Only returns documents that have matches under the specified
+         * properties. If property paths are added for a type, then only the properties referred
+         * to will be searched for results of that type.
+         *
+         * @see #addFilterProperties(String, Collection)
+         *
+         * @param documentClass class annotated with {@link Document}.
+         * @param propertyPaths The {@link PropertyPath} to search search over
+         *
+         */
+        @NonNull
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        @RequiresFeature(
+                enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+                name = Features.SEARCH_SPEC_ADD_FILTER_PROPERTIES)
+        public Builder addFilterPropertyPaths(@NonNull Class<?> documentClass,
+                @NonNull Collection<PropertyPath> propertyPaths) throws AppSearchException {
+            Preconditions.checkNotNull(documentClass);
+            Preconditions.checkNotNull(propertyPaths);
+            resetIfBuilt();
+            DocumentClassFactoryRegistry registry = DocumentClassFactoryRegistry.getInstance();
+            DocumentClassFactory<?> factory = registry.getOrCreateFactory(documentClass);
+            return addFilterPropertyPaths(factory.getSchemaName(), propertyPaths);
+        }
+// @exportToFramework:endStrip()
+
+        /**
          * Adds a namespace filter to {@link SearchSpec} Entry. Only search for documents that
          * have the specified namespaces.
          * <p>If unset, the query will search over all namespaces.
@@ -1478,7 +1652,18 @@
                 }
             }
 
+            Set<String> schemaFilter = new ArraySet<>(mSchemas);
+            if (!mSchemas.isEmpty()) {
+                for (String schema : mTypePropertyFilters.keySet()) {
+                    if (!schemaFilter.contains(schema)) {
+                        throw new IllegalStateException(
+                                "The schema: " + schema + " exists in the property filter but "
+                                        + "doesn't exist in the schema filter.");
+                    }
+                }
+            }
             bundle.putStringArrayList(SCHEMA_FIELD, mSchemas);
+            bundle.putBundle(PROPERTY_FIELD, mTypePropertyFilters);
             bundle.putStringArrayList(NAMESPACE_FIELD, mNamespaces);
             bundle.putStringArrayList(PACKAGE_NAME_FIELD, mPackageNames);
             bundle.putStringArrayList(ENABLED_FEATURES_FIELD, new ArrayList<>(mEnabledFeatures));
@@ -1501,6 +1686,7 @@
         private void resetIfBuilt() {
             if (mBuilt) {
                 mSchemas = new ArrayList<>(mSchemas);
+                mTypePropertyFilters = BundleUtil.deepCopy(mTypePropertyFilters);
                 mNamespaces = new ArrayList<>(mNamespaces);
                 mPackageNames = new ArrayList<>(mPackageNames);
                 mProjectionTypePropertyMasks = BundleUtil.deepCopy(mProjectionTypePropertyMasks);
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/safeparcel/GenericDocumentParcel.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/safeparcel/GenericDocumentParcel.java
new file mode 100644
index 0000000..3d92c02
--- /dev/null
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/safeparcel/GenericDocumentParcel.java
@@ -0,0 +1,494 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appsearch.app.safeparcel;
+
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.appsearch.annotation.CanIgnoreReturnValue;
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.AppSearchSession;
+import androidx.appsearch.app.GenericDocument;
+import androidx.appsearch.safeparcel.AbstractSafeParcelable;
+import androidx.appsearch.safeparcel.SafeParcelable;
+import androidx.appsearch.safeparcel.stub.StubCreators.GenericDocumentParcelCreator;
+import androidx.collection.ArrayMap;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Holds data for a {@link GenericDocument}.
+ *
+ * @exportToFramework:hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SafeParcelable.Class(creator = "GenericDocumentParcelCreator")
+public final class GenericDocumentParcel extends AbstractSafeParcelable {
+    @NonNull
+    public static final GenericDocumentParcelCreator CREATOR =
+            new GenericDocumentParcelCreator();
+
+    /** The default score of document. */
+    private static final int DEFAULT_SCORE = 0;
+
+    /** The default time-to-live in millisecond of a document, which is infinity. */
+    private static final long DEFAULT_TTL_MILLIS = 0L;
+
+    /** Default but invalid value for {@code mCreationTimestampMillis}. */
+    private static final long INVALID_CREATION_TIMESTAMP_MILLIS = -1L;
+
+    @Field(id = 1, getter = "getNamespace")
+    @NonNull
+    private final String mNamespace;
+
+    @Field(id = 2, getter = "getId")
+    @NonNull
+    private final String mId;
+
+    @Field(id = 3, getter = "getSchemaType")
+    @NonNull
+    private final String mSchemaType;
+
+    @Field(id = 4, getter = "getCreationTimestampMillis")
+    private final long mCreationTimestampMillis;
+
+    @Field(id = 5, getter = "getTtlMillis")
+    private final long mTtlMillis;
+
+    @Field(id = 6, getter = "getScore")
+    private final int mScore;
+
+    /**
+     * Contains all properties in {@link GenericDocument} in a list.
+     *
+     * <p>Unfortunately SafeParcelable doesn't support map type so we have to use a list here.
+     */
+    @Field(id = 7, getter = "getProperties")
+    @NonNull
+    private final PropertyParcel[] mProperties;
+
+    /**
+     * Contains all properties in {@link GenericDocument} to support getting properties via name
+     *
+     * <p>This map is created for quick looking up property by name.
+     */
+    @NonNull
+    private final Map<String, PropertyParcel> mPropertyMap;
+
+    @Nullable
+    private Integer mHashCode;
+
+    /**
+     * The constructor taking the property list, and create map internally from this list.
+     *
+     * <p> This will be used in createFromParcel, so creating the property map can not be avoided
+     * in this constructor.
+     */
+    @Constructor
+    GenericDocumentParcel(
+            @Param(id = 1) @NonNull String namespace,
+            @Param(id = 2) @NonNull String id,
+            @Param(id = 3) @NonNull String schemaType,
+            @Param(id = 4) long creationTimestampMillis,
+            @Param(id = 5) long ttlMillis,
+            @Param(id = 6) int score,
+            @Param(id = 7) @NonNull PropertyParcel[] properties) {
+        this(namespace, id, schemaType, creationTimestampMillis, ttlMillis, score,
+                properties, createPropertyMapFromPropertyArray(properties));
+    }
+
+    /**
+     * A constructor taking both property list and property map.
+     *
+     * <p>Caller needs to make sure property list and property map
+     * matches(map is generated from list, or list generated from map).
+     */
+    GenericDocumentParcel(
+            @NonNull String namespace,
+            @NonNull String id,
+            @NonNull String schemaType,
+            long creationTimestampMillis,
+            long ttlMillis,
+            int score,
+            @NonNull PropertyParcel[] properties,
+            @NonNull Map<String, PropertyParcel> propertyMap) {
+        mNamespace = Objects.requireNonNull(namespace);
+        mId = Objects.requireNonNull(id);
+        mSchemaType = Objects.requireNonNull(schemaType);
+        mCreationTimestampMillis = creationTimestampMillis;
+        mTtlMillis = ttlMillis;
+        mScore = score;
+        mProperties = Objects.requireNonNull(properties);
+        mPropertyMap = Objects.requireNonNull(propertyMap);
+    }
+
+    private static Map<String, PropertyParcel> createPropertyMapFromPropertyArray(
+            @NonNull PropertyParcel[] properties) {
+        Objects.requireNonNull(properties);
+        Map<String, PropertyParcel> propertyMap = new ArrayMap<>(properties.length);
+        for (int i = 0; i < properties.length; ++i) {
+            PropertyParcel property = properties[i];
+            propertyMap.put(property.getPropertyName(), property);
+        }
+        return propertyMap;
+    }
+
+    /** Returns the unique identifier of the {@link GenericDocument}. */
+    @NonNull
+    public String getId() {
+        return mId;
+    }
+
+    /** Returns the namespace of the {@link GenericDocument}. */
+    @NonNull
+    public String getNamespace() {
+        return mNamespace;
+    }
+
+    /** Returns the {@link AppSearchSchema} type of the {@link GenericDocument}. */
+    @NonNull
+    public String getSchemaType() {
+        return mSchemaType;
+    }
+
+    /** Returns the creation timestamp of the {@link GenericDocument}, in milliseconds. */
+    /*@exportToFramework:CurrentTimeMillisLong*/
+    public long getCreationTimestampMillis() {
+        return mCreationTimestampMillis;
+    }
+
+    /** Returns the TTL (time-to-live) of the {@link GenericDocument}, in milliseconds. */
+    public long getTtlMillis() {
+        return mTtlMillis;
+    }
+
+    /** Returns the score of the {@link GenericDocument}. */
+    public int getScore() {
+        return mScore;
+    }
+
+    /** Returns the names of all properties defined in this document. */
+    @NonNull
+    public Set<String> getPropertyNames() {
+        return mPropertyMap.keySet();
+    }
+
+    /** Returns all the properties the document has. */
+    @NonNull
+    public PropertyParcel[] getProperties() {
+        return mProperties;
+    }
+
+    /** Returns the property map the document has. */
+    @NonNull
+    public Map<String, PropertyParcel> getPropertyMap() {
+        return mPropertyMap;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (!(other instanceof GenericDocumentParcel)) {
+            return false;
+        }
+        GenericDocumentParcel otherDocument = (GenericDocumentParcel) other;
+        return mNamespace.equals(otherDocument.mNamespace)
+                && mId.equals(otherDocument.mId)
+                && mSchemaType.equals(otherDocument.mSchemaType)
+                && mTtlMillis == otherDocument.mTtlMillis
+                && mCreationTimestampMillis == otherDocument.mCreationTimestampMillis
+                && mScore == otherDocument.mScore
+                && Arrays.equals(mProperties, otherDocument.mProperties)
+                && Objects.equals(mPropertyMap, otherDocument.mPropertyMap);
+    }
+
+    @Override
+    public int hashCode() {
+        if (mHashCode == null) {
+            mHashCode = Objects.hash(
+                    mNamespace,
+                    mId,
+                    mSchemaType,
+                    mTtlMillis,
+                    mScore,
+                    mCreationTimestampMillis,
+                    Arrays.hashCode(mProperties),
+                    mPropertyMap.hashCode());
+        }
+        return mHashCode;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        GenericDocumentParcelCreator.writeToParcel(this, dest, flags);
+    }
+
+    /** The builder class for {@link GenericDocumentParcel}. */
+    public static final class Builder {
+        private String mNamespace;
+        private String mId;
+        private String mSchemaType;
+        private long mCreationTimestampMillis;
+        private long mTtlMillis;
+        private int mScore;
+        private Map<String, PropertyParcel> mPropertyMap;
+        private boolean mBuilt = false;
+
+        /**
+         * Creates a new {@link GenericDocument.Builder}.
+         *
+         * <p>Document IDs are unique within a namespace.
+         *
+         * <p>The number of namespaces per app should be kept small for efficiency reasons.
+         */
+        public Builder(@NonNull String namespace, @NonNull String id, @NonNull String schemaType) {
+            mNamespace = Objects.requireNonNull(namespace);
+            mId = Objects.requireNonNull(id);
+            mSchemaType = Objects.requireNonNull(schemaType);
+            mCreationTimestampMillis = INVALID_CREATION_TIMESTAMP_MILLIS;
+            mTtlMillis = DEFAULT_TTL_MILLIS;
+            mScore = DEFAULT_SCORE;
+            mPropertyMap = new ArrayMap<>();
+        }
+
+        /**
+         * Creates a new {@link GenericDocumentParcel.Builder} from the given
+         * {@link GenericDocumentParcel}.
+         */
+        Builder(@NonNull GenericDocumentParcel documentSafeParcel) {
+            Objects.requireNonNull(documentSafeParcel);
+
+            mNamespace = documentSafeParcel.mNamespace;
+            mId = documentSafeParcel.mId;
+            mSchemaType = documentSafeParcel.mSchemaType;
+            mCreationTimestampMillis = documentSafeParcel.mCreationTimestampMillis;
+            mTtlMillis = documentSafeParcel.mTtlMillis;
+            mScore = documentSafeParcel.mScore;
+
+            // Create a shallow copy of the map so we won't change the original one.
+            Map<String, PropertyParcel> propertyMap = documentSafeParcel.mPropertyMap;
+            mPropertyMap = new ArrayMap<>(propertyMap.size());
+            for (PropertyParcel value : propertyMap.values()) {
+                mPropertyMap.put(value.getPropertyName(), value);
+            }
+        }
+
+        /**
+         * Sets the app-defined namespace this document resides in, changing the value provided in
+         * the constructor. No special values are reserved or understood by the infrastructure.
+         *
+         * <p>Document IDs are unique within a namespace.
+         *
+         * <p>The number of namespaces per app should be kept small for efficiency reasons.
+         */
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder setNamespace(@NonNull String namespace) {
+            Objects.requireNonNull(namespace);
+            resetIfBuilt();
+            mNamespace = namespace;
+            return this;
+        }
+
+        /**
+         * Sets the ID of this document, changing the value provided in the constructor. No special
+         * values are reserved or understood by the infrastructure.
+         *
+         * <p>Document IDs are unique within a namespace.
+         */
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder setId(@NonNull String id) {
+            Objects.requireNonNull(id);
+            resetIfBuilt();
+            mId = id;
+            return this;
+        }
+
+        /**
+         * Sets the schema type of this document, changing the value provided in the constructor.
+         *
+         * <p>To successfully index a document, the schema type must match the name of an {@link
+         * AppSearchSchema} object previously provided to {@link AppSearchSession#setSchema}.
+         */
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder setSchemaType(@NonNull String schemaType) {
+            Objects.requireNonNull(schemaType);
+            resetIfBuilt();
+            mSchemaType = schemaType;
+            return this;
+        }
+
+        /** Sets the score of the parent {@link GenericDocument}. */
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder setScore(int score) {
+            resetIfBuilt();
+            mScore = score;
+            return this;
+        }
+
+        /**
+         * Sets the creation timestamp of the {@link GenericDocument}, in milliseconds.
+         *
+         * <p>This should be set using a value obtained from the {@link System#currentTimeMillis}
+         * time base.
+         *
+         * <p>If this method is not called, this will be set to the time the object is built.
+         *
+         * @param creationTimestampMillis a creation timestamp in milliseconds.
+         */
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder setCreationTimestampMillis(
+                /*@exportToFramework:CurrentTimeMillisLong*/ long creationTimestampMillis) {
+            resetIfBuilt();
+            mCreationTimestampMillis = creationTimestampMillis;
+            return this;
+        }
+
+        /**
+         * Sets the TTL (time-to-live) of the {@link GenericDocument}, in milliseconds.
+         *
+         * <p>The TTL is measured against {@link #getCreationTimestampMillis}. At the timestamp of
+         * {@code creationTimestampMillis + ttlMillis}, measured in the {@link
+         * System#currentTimeMillis} time base, the document will be auto-deleted.
+         *
+         * <p>The default value is 0, which means the document is permanent and won't be
+         * auto-deleted until the app is uninstalled or {@link AppSearchSession#remove} is called.
+         *
+         * @param ttlMillis a non-negative duration in milliseconds.
+         * @throws IllegalArgumentException if ttlMillis is negative.
+         */
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder setTtlMillis(long ttlMillis) {
+            if (ttlMillis < 0) {
+                throw new IllegalArgumentException("Document ttlMillis cannot be negative.");
+            }
+            resetIfBuilt();
+            mTtlMillis = ttlMillis;
+            return this;
+        }
+
+        /**
+         * Clears the value for the property with the given name.
+         *
+         * <p>Note that this method does not support property paths.
+         *
+         * @param name The name of the property to clear.
+         */
+        @CanIgnoreReturnValue
+        @NonNull
+        public Builder clearProperty(@NonNull String name) {
+            Objects.requireNonNull(name);
+            resetIfBuilt();
+            mPropertyMap.remove(name);
+            return this;
+        }
+
+        /** puts an array of {@link String} in property map. */
+        @NonNull
+        public Builder putInPropertyMap(@NonNull String name, @NonNull String[] values)
+                throws IllegalArgumentException {
+            mPropertyMap.put(name,
+                    new PropertyParcel.Builder(name).setStringValues(values).build());
+            return this;
+        }
+
+        /** puts an array of boolean in property map. */
+        @NonNull
+        public Builder putInPropertyMap(@NonNull String name, @NonNull boolean[] values) {
+            mPropertyMap.put(name,
+                    new PropertyParcel.Builder(name).setBooleanValues(values).build());
+            return this;
+        }
+
+        /** puts an array of double in property map. */
+        @NonNull
+        public Builder putInPropertyMap(@NonNull String name, @NonNull double[] values) {
+            mPropertyMap.put(name,
+                    new PropertyParcel.Builder(name).setDoubleValues(values).build());
+            return this;
+        }
+
+        /** puts an array of long in property map. */
+        @NonNull
+        public Builder putInPropertyMap(@NonNull String name, @NonNull long[] values) {
+            mPropertyMap.put(name,
+                    new PropertyParcel.Builder(name).setLongValues(values).build());
+            return this;
+        }
+
+        /**
+         * Converts and saves a byte[][] into {@link #mProperties}.
+         */
+        @NonNull
+        public Builder putInPropertyMap(@NonNull String name, @NonNull byte[][] values) {
+            mPropertyMap.put(name,
+                    new PropertyParcel.Builder(name).setBytesValues(values).build());
+            return this;
+        }
+
+        /** puts an array of {@link GenericDocumentParcel} in property map. */
+        @NonNull
+        public Builder putInPropertyMap(@NonNull String name,
+                @NonNull GenericDocumentParcel[] values) {
+            mPropertyMap.put(name,
+                    new PropertyParcel.Builder(name).setDocumentValues(values).build());
+            return this;
+        }
+
+        /** Builds the {@link GenericDocument} object. */
+        @NonNull
+        public GenericDocumentParcel build() {
+            mBuilt = true;
+            // Set current timestamp for creation timestamp by default.
+            if (mCreationTimestampMillis == INVALID_CREATION_TIMESTAMP_MILLIS) {
+                mCreationTimestampMillis = System.currentTimeMillis();
+            }
+            return new GenericDocumentParcel(
+                    mNamespace,
+                    mId,
+                    mSchemaType,
+                    mCreationTimestampMillis,
+                    mTtlMillis,
+                    mScore,
+                    mPropertyMap.values().toArray(new PropertyParcel[0]));
+        }
+
+        void resetIfBuilt() {
+            if (mBuilt) {
+                Map<String, PropertyParcel> propertyMap = mPropertyMap;
+                mPropertyMap = new ArrayMap<>(propertyMap.size());
+                for (PropertyParcel value : propertyMap.values()) {
+                    // PropertyParcel is not deep copied since it is not mutable.
+                    mPropertyMap.put(value.getPropertyName(), value);
+                }
+                mBuilt = false;
+            }
+        }
+    }
+}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/safeparcel/PropertyConfigParcel.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/safeparcel/PropertyConfigParcel.java
new file mode 100644
index 0000000..f80a575
--- /dev/null
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/safeparcel/PropertyConfigParcel.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appsearch.app.safeparcel;
+
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.appsearch.app.AppSearchSchema;
+import androidx.appsearch.app.AppSearchSchema.PropertyConfig.Cardinality;
+import androidx.appsearch.app.AppSearchSchema.PropertyConfig.DataType;
+import androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.JoinableValueType;
+import androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.TokenizerType;
+import androidx.appsearch.safeparcel.AbstractSafeParcelable;
+import androidx.appsearch.safeparcel.SafeParcelable;
+import androidx.appsearch.safeparcel.stub.StubCreators.DocumentIndexingConfigParcelCreator;
+import androidx.appsearch.safeparcel.stub.StubCreators.IntegerIndexingConfigParcelCreator;
+import androidx.appsearch.safeparcel.stub.StubCreators.JoinableConfigParcelCreator;
+import androidx.appsearch.safeparcel.stub.StubCreators.PropertyConfigParcelCreator;
+import androidx.appsearch.safeparcel.stub.StubCreators.StringIndexingConfigParcelCreator;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Class to hold property configuration for one property defined in {@link AppSearchSchema}.
+ *
+ * <p>It is defined as same as PropertyConfigProto for the native code to handle different property
+ * types in one class.
+ *
+ * <p>Currently it can handle String, long, double, boolean, bytes and document type.
+ *
+ * @exportToFramework:hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SafeParcelable.Class(creator = "PropertyConfigParcelCreator")
+public final class PropertyConfigParcel extends AbstractSafeParcelable {
+    @NonNull
+    public static final PropertyConfigParcelCreator CREATOR = new PropertyConfigParcelCreator();
+
+    @Field(id = 1, getter = "getName")
+    private final String mName;
+
+    @AppSearchSchema.PropertyConfig.DataType
+    @Field(id = 2, getter = "getDataType")
+    private final int mDataType;
+
+    @AppSearchSchema.PropertyConfig.Cardinality
+    @Field(id = 3, getter = "getCardinality")
+    private final int mCardinality;
+
+    @Field(id = 4, getter = "getSchemaType")
+    private final String mSchemaType;
+
+    @Field(id = 5, getter = "getStringIndexingConfigParcel")
+    private final StringIndexingConfigParcel mStringIndexingConfigParcel;
+
+    @Field(id = 6, getter = "getDocumentIndexingConfigParcel")
+    private final DocumentIndexingConfigParcel mDocumentIndexingConfigParcel;
+
+    @Field(id = 7, getter = "getIntegerIndexingConfigParcel")
+    private final IntegerIndexingConfigParcel mIntegerIndexingConfigParcel;
+
+    @Field(id = 8, getter = "getJoinableConfigParcel")
+    private final JoinableConfigParcel mJoinableConfigParcel;
+
+    /** Constructor for {@link PropertyConfigParcel}. */
+    @Constructor
+    public PropertyConfigParcel(
+            @Param(id = 1) @NonNull String name,
+            @Param(id = 2) @DataType int dataType,
+            @Param(id = 3) @Cardinality int cardinality,
+            @Param(id = 4) @Nullable String schemaType,
+            @Param(id = 5) @Nullable StringIndexingConfigParcel stringIndexingConfigParcel,
+            @Param(id = 6) @Nullable DocumentIndexingConfigParcel documentIndexingConfigParcel,
+            @Param(id = 7) @Nullable IntegerIndexingConfigParcel integerIndexingConfigParcel,
+            @Param(id = 8) @Nullable JoinableConfigParcel joinableConfigParcel) {
+        mName = Objects.requireNonNull(name);
+        mDataType = dataType;
+        mCardinality = cardinality;
+        mSchemaType = schemaType;
+        mStringIndexingConfigParcel = stringIndexingConfigParcel;
+        mDocumentIndexingConfigParcel = documentIndexingConfigParcel;
+        mIntegerIndexingConfigParcel = integerIndexingConfigParcel;
+        mJoinableConfigParcel = joinableConfigParcel;
+    }
+
+    /** Gets name for the property. */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    /** Gets data type for the property. */
+    @DataType
+    public int getDataType() {
+        return mDataType;
+    }
+
+    /** Gets cardinality for the property. */
+    @Cardinality
+    public int getCardinality() {
+        return mCardinality;
+    }
+
+    /** Gets schema type. */
+    @Nullable
+    public String getSchemaType() {
+        return mSchemaType;
+    }
+
+    /** Gets the {@link StringIndexingConfigParcel}. */
+    @Nullable
+    public StringIndexingConfigParcel getStringIndexingConfigParcel() {
+        return mStringIndexingConfigParcel;
+    }
+
+    /** Gets the {@link DocumentIndexingConfigParcel}. */
+    @Nullable
+    public DocumentIndexingConfigParcel getDocumentIndexingConfigParcel() {
+        return mDocumentIndexingConfigParcel;
+    }
+
+    /** Gets the {@link IntegerIndexingConfigParcel}. */
+    @Nullable
+    public IntegerIndexingConfigParcel getIntegerIndexingConfigParcel() {
+        return mIntegerIndexingConfigParcel;
+    }
+
+    /** Gets the {@link JoinableConfigParcel}. */
+    @Nullable
+    public JoinableConfigParcel getJoinableConfigParcel() {
+        return mJoinableConfigParcel;
+    }
+
+    /** Class to hold join configuration for a String type. */
+    @SafeParcelable.Class(creator = "JoinableConfigParcelCreator")
+    public static class JoinableConfigParcel extends AbstractSafeParcelable {
+        @NonNull
+        public static final JoinableConfigParcelCreator CREATOR = new JoinableConfigParcelCreator();
+
+        @JoinableValueType
+        @Field(id = 1, getter = "getJoinableValueType")
+        private final int mJoinableValueType;
+
+        @Field(id = 2, getter = "getDeletionPropagation")
+        private final boolean mDeletionPropagation;
+
+        /** Constructor for {@link JoinableConfigParcel}. */
+        @Constructor
+        public JoinableConfigParcel(
+                @Param(id = 1) @JoinableValueType int joinableValueType,
+                @Param(id = 2) boolean deletionPropagation) {
+            mJoinableValueType = joinableValueType;
+            mDeletionPropagation = deletionPropagation;
+        }
+
+        /** Gets {@link JoinableValueType} of the join. */
+        @JoinableValueType
+        public int getJoinableValueType() {
+            return mJoinableValueType;
+        }
+
+        /** Gets whether delete will be propagated. */
+        public boolean getDeletionPropagation() {
+            return mDeletionPropagation;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            JoinableConfigParcelCreator.writeToParcel(this, dest, flags);
+        }
+    }
+
+    /** Class to hold configuration a string type. */
+    @SafeParcelable.Class(creator = "StringIndexingConfigParcelCreator")
+    public static class StringIndexingConfigParcel extends AbstractSafeParcelable {
+        @NonNull
+        public static final StringIndexingConfigParcelCreator CREATOR =
+                new StringIndexingConfigParcelCreator();
+
+        @AppSearchSchema.StringPropertyConfig.IndexingType
+        @Field(id = 1, getter = "getIndexingType")
+        private final int mIndexingType;
+
+        @TokenizerType
+        @Field(id = 2, getter = "getTokenizerType")
+        private final int mTokenizerType;
+
+        /** Constructor for {@link StringIndexingConfigParcel}. */
+        @Constructor
+        public StringIndexingConfigParcel(
+                @Param(id = 1) @AppSearchSchema.StringPropertyConfig.IndexingType int indexingType,
+                @Param(id = 2) @TokenizerType int tokenizerType) {
+            mIndexingType = indexingType;
+            mTokenizerType = tokenizerType;
+        }
+
+        /** Gets the indexing type for this property. */
+        @AppSearchSchema.StringPropertyConfig.IndexingType
+        public int getIndexingType() {
+            return mIndexingType;
+        }
+
+        /** Gets the tokenization type for this property. */
+        @TokenizerType
+        public int getTokenizerType() {
+            return mTokenizerType;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            StringIndexingConfigParcelCreator.writeToParcel(this, dest, flags);
+        }
+    }
+
+    /** Class to hold configuration for integer property type. */
+    @SafeParcelable.Class(creator = "IntegerIndexingConfigParcelCreator")
+    public static class IntegerIndexingConfigParcel extends AbstractSafeParcelable {
+        @NonNull
+        public static final IntegerIndexingConfigParcelCreator CREATOR =
+                new IntegerIndexingConfigParcelCreator();
+
+        @AppSearchSchema.LongPropertyConfig.IndexingType
+        @Field(id = 1, getter = "getIndexingType")
+        private final int mIndexingType;
+
+        /** Constructor for {@link IntegerIndexingConfigParcel}. */
+        @Constructor
+        public IntegerIndexingConfigParcel(
+                @Param(id = 1) @AppSearchSchema.LongPropertyConfig.IndexingType int indexingType) {
+            mIndexingType = indexingType;
+        }
+
+        /** Gets the indexing type for this integer property. */
+        @AppSearchSchema.LongPropertyConfig.IndexingType
+        public int getIndexingType() {
+            return mIndexingType;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            IntegerIndexingConfigParcelCreator.writeToParcel(this, dest, flags);
+        }
+    }
+
+    /** Class to hold configuration for document property type. */
+    @SafeParcelable.Class(creator = "DocumentIndexingConfigParcelCreator")
+    public static class DocumentIndexingConfigParcel extends AbstractSafeParcelable {
+        @NonNull
+        public static final DocumentIndexingConfigParcelCreator CREATOR =
+                new DocumentIndexingConfigParcelCreator();
+
+        @Field(id = 1, getter = "shouldIndexNestedProperties")
+        private final boolean mIndexNestedProperties;
+
+        @NonNull
+        @Field(id = 2, getter = "getIndexableNestedPropertiesList")
+        private final List<String> mIndexableNestedPropertiesList;
+
+        /** Constructor for {@link DocumentIndexingConfigParcel}. */
+        @Constructor
+        public DocumentIndexingConfigParcel(
+                @Param(id = 1) boolean indexNestedProperties,
+                @Param(id = 2) @NonNull List<String> indexableNestedPropertiesList) {
+            mIndexNestedProperties = indexNestedProperties;
+            mIndexableNestedPropertiesList = Objects.requireNonNull(indexableNestedPropertiesList);
+        }
+
+        /** Nested properties should be indexed. */
+        public boolean shouldIndexNestedProperties() {
+            return mIndexNestedProperties;
+        }
+
+        /** Gets the list for nested property list. */
+        @NonNull
+        public List<String> getIndexableNestedPropertiesList() {
+            return mIndexableNestedPropertiesList;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            DocumentIndexingConfigParcelCreator.writeToParcel(this, dest, flags);
+        }
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        PropertyConfigParcelCreator.writeToParcel(this, dest, flags);
+    }
+}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/PropertyParcel.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/safeparcel/PropertyParcel.java
similarity index 88%
rename from appsearch/appsearch/src/main/java/androidx/appsearch/app/PropertyParcel.java
rename to appsearch/appsearch/src/main/java/androidx/appsearch/app/safeparcel/PropertyParcel.java
index 7e6fa88..f9034a9 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/PropertyParcel.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/safeparcel/PropertyParcel.java
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package androidx.appsearch.app;
+package androidx.appsearch.app.safeparcel;
 
 
-import android.os.Bundle;
 import android.os.Parcel;
 
 import androidx.annotation.NonNull;
@@ -26,7 +25,6 @@
 import androidx.appsearch.safeparcel.AbstractSafeParcelable;
 import androidx.appsearch.safeparcel.SafeParcelable;
 import androidx.appsearch.safeparcel.stub.StubCreators.PropertyParcelCreator;
-import androidx.appsearch.util.BundleUtil;
 
 import java.util.Arrays;
 import java.util.Objects;
@@ -67,10 +65,9 @@
     @Field(id = 6, getter = "getBytesValues")
     private final byte[][] mBytesValues;
 
-    // TODO(b/24205844) Change it to GenericDocumentParcel once it is added.
     @Nullable
     @Field(id = 7, getter = "getDocumentValues")
-    private final Bundle[] mDocumentValues;
+    private final GenericDocumentParcel[] mDocumentValues;
 
     @Nullable private Integer mHashCode;
 
@@ -82,7 +79,7 @@
             @Param(id = 4) @Nullable double[] doubleValues,
             @Param(id = 5) @Nullable boolean[] booleanValues,
             @Param(id = 6) @Nullable byte[][] bytesValues,
-            @Param(id = 7) @Nullable Bundle[] documentValues) {
+            @Param(id = 7) @Nullable GenericDocumentParcel[] documentValues) {
         mPropertyName = Objects.requireNonNull(propertyName);
         mStringValues = stringValues;
         mLongValues = longValues;
@@ -129,9 +126,9 @@
         return mBytesValues;
     }
 
-    /** Returns {@link Bundle} in an array. */
+    /** Returns {@link GenericDocumentParcel}s in an array. */
     @Nullable
-    public Bundle[] getDocumentValues() {
+    public GenericDocumentParcel[] getDocumentValues() {
         return mDocumentValues;
     }
 
@@ -209,13 +206,7 @@
             } else if (mBytesValues != null) {
                 hashCode = Arrays.deepHashCode(mBytesValues);
             } else if (mDocumentValues != null) {
-                // TODO(b/24205844) change those to Arrays.hashCode() as well once we replace
-                //  this Bundle[] with GenericDocumentParcel[].
-                int[] innerHashCodes = new int[mDocumentValues.length];
-                for (int i = 0; i < mDocumentValues.length; ++i) {
-                    innerHashCodes[i] = BundleUtil.deepHashCode(mDocumentValues[i]);
-                }
-                hashCode = Arrays.hashCode(innerHashCodes);
+                hashCode = Arrays.hashCode(mDocumentValues);
             }
             mHashCode = Objects.hash(mPropertyName, hashCode);
         }
@@ -239,9 +230,7 @@
                 && Arrays.equals(mDoubleValues, otherPropertyParcel.mDoubleValues)
                 && Arrays.equals(mBooleanValues, otherPropertyParcel.mBooleanValues)
                 && Arrays.deepEquals(mBytesValues, otherPropertyParcel.mBytesValues)
-                // TODO(b/24205844) Change it to Arrays.equals once GenericDocumentParcel is added.
-                && BundleUtil.bundleValueEquals(
-                mDocumentValues, otherPropertyParcel.mDocumentValues);
+                && Arrays.equals(mDocumentValues, otherPropertyParcel.mDocumentValues);
     }
 
     /** Builder for {@link PropertyParcel}. */
@@ -252,7 +241,7 @@
         private double[] mDoubleValues;
         private boolean[] mBooleanValues;
         private byte[][] mBytesValues;
-        private Bundle[] mDocumentValues;
+        private GenericDocumentParcel[] mDocumentValues;
 
         public Builder(@NonNull String propertyName) {
             mPropertyName = Objects.requireNonNull(propertyName);
@@ -295,7 +284,7 @@
 
         /** Sets document values. */
         @NonNull
-        public Builder setDocumentValues(@NonNull Bundle[] documentValues) {
+        public Builder setDocumentValues(@NonNull GenericDocumentParcel[] documentValues) {
             mDocumentValues = Objects.requireNonNull(documentValues);
             return this;
         }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/safeparcel/stub/StubCreators.java b/appsearch/appsearch/src/main/java/androidx/appsearch/safeparcel/stub/StubCreators.java
index 63c31df2..c4f9241 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/safeparcel/stub/StubCreators.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/safeparcel/stub/StubCreators.java
@@ -16,6 +16,9 @@
 package androidx.appsearch.safeparcel.stub;
 
 import androidx.annotation.RestrictTo;
+import androidx.appsearch.app.safeparcel.GenericDocumentParcel;
+import androidx.appsearch.app.safeparcel.PropertyConfigParcel;
+import androidx.appsearch.app.safeparcel.PropertyParcel;
 
 /**
  * Stub creators for any classes extending
@@ -32,7 +35,43 @@
     public static class StorageInfoCreator extends AbstractCreator {
     }
 
-    /** Stub creator for {@link androidx.appsearch.app.PropertyParcel}. */
+    /** Stub creator for {@link PropertyParcel}. */
     public static class PropertyParcelCreator extends AbstractCreator {
     }
+
+    /** Stub creator for {@link PropertyConfigParcel}. */
+    public static class PropertyConfigParcelCreator extends AbstractCreator {
+    }
+
+    /**
+     * Stub creator for
+     * {@link PropertyConfigParcel.JoinableConfigParcel}.
+     */
+    public static class JoinableConfigParcelCreator extends AbstractCreator {
+    }
+
+    /**
+     * Stub creator for
+     * {@link PropertyConfigParcel.StringIndexingConfigParcel}.
+     */
+    public static class StringIndexingConfigParcelCreator extends AbstractCreator {
+    }
+
+    /**
+     * Stub creator for
+     * {@link PropertyConfigParcel.IntegerIndexingConfigParcel}.
+     */
+    public static class IntegerIndexingConfigParcelCreator extends AbstractCreator {
+    }
+
+    /**
+     * Stub creator for
+     * {@link PropertyConfigParcel.DocumentIndexingConfigParcel}.
+     */
+    public static class DocumentIndexingConfigParcelCreator extends AbstractCreator {
+    }
+
+    /** Stub creator for {@link GenericDocumentParcel}. */
+    public static class GenericDocumentParcelCreator extends AbstractCreator {
+    }
 }
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/InstrumentationResults.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/InstrumentationResults.kt
index d778a9f..bae0b4c 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/InstrumentationResults.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/InstrumentationResults.kt
@@ -298,6 +298,9 @@
         absoluteFilePath: String,
         reportOnRunEndOnly: Boolean = false
     ) {
+        require(!key.contains('=')) {
+            "Key must not contain '=', which breaks instrumentation result string parsing"
+        }
         if (reportOnRunEndOnly) {
             InstrumentationResultScope(runEndResultBundle).fileRecord(key, absoluteFilePath)
         } else {
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Outputs.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Outputs.kt
index 53c59e4..44262c6 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Outputs.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Outputs.kt
@@ -117,7 +117,6 @@
      */
     fun writeFile(
         fileName: String,
-        reportKey: String,
         reportOnRunEndOnly: Boolean = false,
         block: (file: File) -> Unit,
     ): String {
@@ -143,7 +142,7 @@
         }
 
         InstrumentationResults.reportAdditionalFileToCopy(
-            key = reportKey,
+            key = sanitizedName,
             absoluteFilePath = destination.absolutePath,
             reportOnRunEndOnly = reportOnRunEndOnly
         )
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
index 19d47fc..1a7b9308 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt
@@ -293,8 +293,7 @@
     override fun stop() {
         session!!.stopRecording()
         Outputs.writeFile(
-            fileName = outputRelativePath!!,
-            reportKey = "simpleperf_trace"
+            fileName = outputRelativePath!!
         ) {
             session!!.convertSimpleperfOutputToProto("simpleperf.data", it.absolutePath)
         }
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/ResultWriter.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/ResultWriter.kt
index 873ead3..03c8c81 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/ResultWriter.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/ResultWriter.kt
@@ -43,7 +43,6 @@
 
             Outputs.writeFile(
                 fileName = "$packageName-benchmarkData.json",
-                reportKey = "results_json",
                 reportOnRunEndOnly = true
             ) {
                 Log.d(
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt
index 5eaa17c..22529eb 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt
@@ -76,8 +76,7 @@
     @RequiresApi(23)
     private fun stop(traceLabel: String): String {
         return Outputs.writeFile(
-            fileName = "${traceLabel}_${dateToFileName()}.perfetto-trace",
-            reportKey = "perfetto_trace_$traceLabel"
+            fileName = "${traceLabel}_${dateToFileName()}.perfetto-trace"
         ) {
             capture!!.stop(it.absolutePath)
             if (Outputs.forceFilesForShellAccessible) {
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
index b4fdbc1..6c67eab 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
@@ -421,8 +421,6 @@
                     // supported by a device. That is why we need to search from most specific to
                     // least specific. For e.g. emulators claim to support aarch64, when in reality
                     // they can only support x86 or x86_64.
-                    // Note: Cuttlefish is x86 but claims support for x86_64
-                    Build.MODEL.contains("Cuttlefish") -> "x86" // TODO(204892353): handle properly
                     Build.SUPPORTED_64_BIT_ABIS.any { it.startsWith("x86_64") } -> "x86_64"
                     Build.SUPPORTED_32_BIT_ABIS.any { it.startsWith("x86") } -> "x86"
                     Build.SUPPORTED_64_BIT_ABIS.any { it.startsWith("arm64") } -> "aarch64"
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
index bb55014..1514db6 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/BaselineProfiles.kt
@@ -190,23 +190,21 @@
     // Write a file with a timestamp to be able to disambiguate between runs with the same
     // unique name.
 
-    val (fileName, reportKey, tsFileName) =
+    val (fileName, tsFileName) =
         if (includeInStartupProfile && Arguments.enableStartupProfiles) {
             arrayOf(
                 "$uniqueFilePrefix-startup-prof.txt",
-                "startup-profile",
                 "$uniqueFilePrefix-startup-prof-${Outputs.dateToFileName()}.txt"
             )
         } else {
             arrayOf(
                 "$uniqueFilePrefix-baseline-prof.txt",
-                "baseline-profile",
                 "$uniqueFilePrefix-baseline-prof-${Outputs.dateToFileName()}.txt"
             )
         }
 
-    val absolutePath = Outputs.writeFile(fileName, reportKey) { it.writeText(profile) }
-    val tsAbsolutePath = Outputs.writeFile(tsFileName, "baseline-profile-ts") {
+    val absolutePath = Outputs.writeFile(fileName) { it.writeText(profile) }
+    val tsAbsolutePath = Outputs.writeFile(tsFileName) {
         Log.d(TAG, "Pull Baseline Profile with: `adb pull \"${it.absolutePath}\" .`")
         it.writeText(profile)
     }
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
index 9b141c6..5990a47 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
@@ -332,7 +332,7 @@
         // Staging location before we write it again using Outputs.writeFile(...)
         Shell.executeScriptSilent("cp '$tracePath' '$stagingFile'")
         // Report(
-        val outputPath = Outputs.writeFile(fileName, fileName) {
+        val outputPath = Outputs.writeFile(fileName) {
             Log.d(TAG, "Writing method traces to ${it.absolutePath}")
             stagingFile.copyTo(it, overwrite = true)
             // Cleanup
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MethodTracing.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MethodTracing.kt
index f77efc40e..790413a 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MethodTracing.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MethodTracing.kt
@@ -60,7 +60,7 @@
         val stagingPath = "${Outputs.dirUsableByAppAndShell}/_$fileName"
         Shell.executeScriptSilent("cp '$sourcePath' '$stagingPath'")
         // Report
-        Outputs.writeFile(fileName, fileName) {
+        Outputs.writeFile(fileName) {
             val staging = File(stagingPath)
             // No need to clean up, here because the clean up happens automatically on subsequent
             // test runs.
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/AudioUnderrunQuery.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/AudioUnderrunQuery.kt
index 692175f..5a08436 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/AudioUnderrunQuery.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/AudioUnderrunQuery.kt
@@ -22,7 +22,7 @@
 internal object AudioUnderrunQuery {
     @Language("sql")
     private fun getFullQuery() = """
-        SELECT track.name, counter.value, counter.ts
+        SELECT counter.value, counter.ts
         FROM track
         JOIN counter ON track.id = counter.track_id
         WHERE track.type = 'process_counter_track' AND track.name LIKE 'nRdy%'
@@ -38,7 +38,6 @@
     ): SubMetrics {
         val queryResult = session.query(getFullQuery())
 
-        var trackName: String? = null
         var lastTs: Long? = null
         var totalNs: Long = 0
         var zeroNs: Long = 0
@@ -46,16 +45,9 @@
         queryResult
             .asSequence()
             .forEach { lineVals ->
-
                 if (lineVals.size != EXPECTED_COLUMN_COUNT)
                     throw IllegalStateException("query failed")
 
-                if (trackName == null) {
-                    trackName = lineVals[VAL_NAME] as String?
-                } else if (trackName!! != lineVals[VAL_NAME]) {
-                    throw RuntimeException("There could be only one AudioTrack per measure")
-                }
-
                 if (lastTs == null) {
                     lastTs = lineVals[VAL_TS] as Long
                 } else {
@@ -74,8 +66,7 @@
         return SubMetrics((totalNs / 1_000_000).toInt(), (zeroNs / 1_000_000).toInt())
     }
 
-    private const val VAL_NAME = "name"
     private const val VAL_VALUE = "value"
     private const val VAL_TS = "ts"
-    private const val EXPECTED_COLUMN_COUNT = 3
+    private const val EXPECTED_COLUMN_COUNT = 2
 }
diff --git a/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/AudioUnderrunBenchmark.kt b/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/AudioUnderrunBenchmark.kt
index 8229714..9a9791e 100644
--- a/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/AudioUnderrunBenchmark.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/AudioUnderrunBenchmark.kt
@@ -27,7 +27,6 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -35,7 +34,7 @@
 @LargeTest
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalMetricApi::class)
-class AudioUnderrunBenchmark() {
+class AudioUnderrunBenchmark {
     @get:Rule
     val benchmarkRule = MacrobenchmarkRule()
 
@@ -48,7 +47,6 @@
     }
 
     @Test
-    @Ignore("b/297916125")
     fun start() {
         benchmarkRule.measureRepeated(
             packageName = PACKAGE_NAME,
diff --git a/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileRuleTest.kt b/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileRuleTest.kt
index c252b60..cfaa555 100644
--- a/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileRuleTest.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/BaselineProfileRuleTest.kt
@@ -29,6 +29,7 @@
 import org.junit.Assert.assertTrue
 import org.junit.Assert.fail
 import org.junit.Assume.assumeTrue
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 
@@ -42,6 +43,7 @@
     private val filterRegex = "^.*L${PACKAGE_NAME.replace(".", "/")}".toRegex()
 
     @Test
+    @Ignore("b/294123161")
     fun appNotInstalled() {
         val error = assertFailsWith<AssertionError> {
             baselineRule.collect(
@@ -56,6 +58,7 @@
     }
 
     @Test
+    @Ignore("b/294123161")
     fun filter() {
         // TODO: share this 'is supported' check with the one inside BaselineProfileRule, once this
         //  test class is moved out of integration-tests, into benchmark-macro-junit4
@@ -90,6 +93,7 @@
     }
 
     @Test
+    @Ignore("b/294123161")
     fun profileType() {
         assumeTrue(Build.VERSION.SDK_INT >= 33 || Shell.isSessionRooted())
 
diff --git a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricAdvertiseTest.kt b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricAdvertiseTest.kt
index 34276ac..8c6a2e1 100644
--- a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricAdvertiseTest.kt
+++ b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricAdvertiseTest.kt
@@ -21,7 +21,7 @@
 import androidx.bluetooth.AdvertiseResult
 import androidx.bluetooth.BluetoothLe
 import java.util.UUID
-import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert
@@ -40,8 +40,10 @@
     fun advertiseSuccess() = runTest {
         val params = AdvertiseParams()
         launch {
-            val result = bluetoothLe.advertise(params).first()
-            Assert.assertEquals(AdvertiseResult.ADVERTISE_STARTED, result)
+            bluetoothLe.advertise(params) { result ->
+                Assert.assertEquals(AdvertiseResult.ADVERTISE_STARTED, result)
+                cancel()
+            }
         }
     }
 
@@ -59,8 +61,9 @@
         )
 
         launch {
-            val result = bluetoothLe.advertise(advertiseParams).first()
-            Assert.assertEquals(AdvertiseResult.ADVERTISE_FAILED_DATA_TOO_LARGE, result)
+            bluetoothLe.advertise(advertiseParams) { result ->
+                Assert.assertEquals(AdvertiseResult.ADVERTISE_FAILED_DATA_TOO_LARGE, result)
+            }
         }
     }
 }
diff --git a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattClientTest.kt b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattClientTest.kt
index 7bab0cd..32729d4 100644
--- a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattClientTest.kt
+++ b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattClientTest.kt
@@ -28,6 +28,7 @@
 import android.bluetooth.BluetoothGattService as FwkService
 import android.bluetooth.BluetoothManager
 import android.content.Context
+import android.os.Build
 import androidx.bluetooth.BluetoothDevice
 import androidx.bluetooth.BluetoothLe
 import androidx.bluetooth.GattClient
@@ -37,9 +38,12 @@
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.flow.toList
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert
+import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -109,23 +113,22 @@
 
         acceptConnect()
 
-        Assert.assertEquals(true, bluetoothLe.connectGatt(device) {
-            Assert.assertEquals(sampleServices.size, getServices().size)
+        bluetoothLe.connectGatt(device) {
+            Assert.assertEquals(sampleServices.size, services.size)
             sampleServices.forEachIndexed { index, service ->
-                Assert.assertEquals(service.uuid, getServices()[index].uuid)
+                Assert.assertEquals(service.uuid, services[index].uuid)
             }
-            awaitClose { closed.complete(Unit) }
-            true
-        }.getOrNull())
+            closed.complete(Unit)
+        }
 
-        Assert.assertTrue(closed.isCompleted)
+        assertTrue(closed.isCompleted)
     }
 
     @Test
     fun connectFail() = runTest {
         val device = createDevice("00:11:22:33:44:55")
         rejectConnect()
-        Assert.assertEquals(true, bluetoothLe.connectGatt(device) { true }.isFailure)
+        assertTrue(runCatching { bluetoothLe.connectGatt(device) { } }.isFailure)
     }
 
     @Test
@@ -150,16 +153,14 @@
         }
 
         bluetoothLe.connectGatt(device) {
-            Assert.assertEquals(sampleServices.size, getServices().size)
+            Assert.assertEquals(sampleServices.size, services.size)
             Assert.assertEquals(testValue,
                 readCharacteristic(
-                    getServices()[0].getCharacteristic(readCharUuid)!!
+                    services[0].getCharacteristic(readCharUuid)!!
                 ).getOrNull()?.toInt())
-            awaitClose {
-                closed.complete(Unit)
-            }
+            closed.complete(Unit)
         }
-        Assert.assertTrue(closed.isCompleted)
+        assertTrue(closed.isCompleted)
     }
 
     @Test
@@ -174,10 +175,10 @@
             }
 
         bluetoothLe.connectGatt(device) {
-            Assert.assertEquals(sampleServices.size, getServices().size)
-            Assert.assertTrue(
+            Assert.assertEquals(sampleServices.size, services.size)
+            assertTrue(
                 readCharacteristic(
-                    getServices()[0].getCharacteristic(noPropertyCharUuid)!!
+                    services[0].getCharacteristic(noPropertyCharUuid)!!
                 ).exceptionOrNull()
                 is IllegalArgumentException)
         }
@@ -219,8 +220,8 @@
         }
 
         bluetoothLe.connectGatt(device) {
-            Assert.assertEquals(sampleServices.size, getServices().size)
-            val characteristic = getServices()[0].getCharacteristic(writeCharUuid)!!
+            Assert.assertEquals(sampleServices.size, services.size)
+            val characteristic = services[0].getCharacteristic(writeCharUuid)!!
 
             Assert.assertEquals(initialValue,
                 readCharacteristic(characteristic).getOrNull()?.toInt())
@@ -228,11 +229,9 @@
                 valueToWrite.toByteArray())
             Assert.assertEquals(valueToWrite,
                 readCharacteristic(characteristic).getOrNull()?.toInt())
-            awaitClose {
-                closed.complete(Unit)
-            }
+            closed.complete(Unit)
         }
-        Assert.assertTrue(closed.isCompleted)
+        assertTrue(closed.isCompleted)
     }
 
     @Test
@@ -247,10 +246,10 @@
             }
 
         bluetoothLe.connectGatt(device) {
-            Assert.assertEquals(sampleServices.size, getServices().size)
-            Assert.assertTrue(
+            Assert.assertEquals(sampleServices.size, services.size)
+            assertTrue(
                 writeCharacteristic(
-                    getServices()[0].getCharacteristic(readCharUuid)!!,
+                    services[0].getCharacteristic(readCharUuid)!!,
                     48.toByteArray()
                 ).exceptionOrNull()
                 is IllegalArgumentException)
@@ -295,8 +294,8 @@
         }
 
         bluetoothLe.connectGatt(device) {
-            Assert.assertEquals(sampleServices.size, getServices().size)
-            val characteristic = getServices()[0].getCharacteristic(notifyCharUuid)!!
+            Assert.assertEquals(sampleServices.size, services.size)
+            val characteristic = services[0].getCharacteristic(notifyCharUuid)!!
 
             Assert.assertEquals(initialValue,
                 readCharacteristic(characteristic).getOrNull()?.toInt())
@@ -305,11 +304,9 @@
                 subscribeToCharacteristic(characteristic).first().toInt())
             Assert.assertEquals(valueToNotify,
                 readCharacteristic(characteristic).getOrNull()?.toInt())
-            awaitClose {
-                closed.complete(Unit)
-            }
+            closed.complete(Unit)
         }
-        Assert.assertTrue(closed.isCompleted)
+        assertTrue(closed.isCompleted)
     }
 
     @Test
@@ -324,9 +321,9 @@
             }
 
         bluetoothLe.connectGatt(device) {
-            Assert.assertEquals(sampleServices.size, getServices().size)
+            Assert.assertEquals(sampleServices.size, services.size)
             subscribeToCharacteristic(
-                getServices()[0].getCharacteristic(readCharUuid)!!,
+                services[0].getCharacteristic(readCharUuid)!!,
             ).collect {
                 // Should not be notified
                 fail()
@@ -334,6 +331,41 @@
         }
     }
 
+    @Test
+    fun servicesFlow_emittedWhenServicesChange() = runTest {
+        val device = createDevice("00:11:22:33:44:55")
+
+        val newServiceUuid = UUID.randomUUID()
+        val newService = FwkService(newServiceUuid, FwkService.SERVICE_TYPE_PRIMARY)
+        val newServices = sampleServices + newService
+
+        acceptConnect()
+
+        clientAdapter.onDiscoverServicesListener =
+            StubClientFrameworkAdapter.OnDiscoverServicesListener {
+                if (clientAdapter.gattServices.isEmpty()) {
+                    clientAdapter.gattServices = sampleServices
+                }
+                clientAdapter.callback?.onServicesDiscovered(
+                    clientAdapter.bluetoothGatt,
+                    BluetoothGatt.GATT_SUCCESS
+                )
+            }
+
+        bluetoothLe.connectGatt(device) {
+            launch {
+                clientAdapter.gattServices = newServices
+                if (Build.VERSION.SDK_INT >= 31) {
+                    clientAdapter.callback?.onServiceChanged(clientAdapter.bluetoothGatt!!)
+                }
+            }
+            val servicesEmitted = servicesFlow.take(2).toList()
+            Assert.assertEquals(sampleServices.size, servicesEmitted[0].size)
+            Assert.assertEquals(sampleServices.size + 1, servicesEmitted[1].size)
+            Assert.assertEquals(newServiceUuid, servicesEmitted[1][sampleServices.size].uuid)
+        }
+    }
+
     private fun acceptConnect() {
         clientAdapter.onConnectListener =
             StubClientFrameworkAdapter.OnConnectListener { device, _ ->
diff --git a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattServerTest.kt b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattServerTest.kt
index fea074b..741377c 100644
--- a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattServerTest.kt
+++ b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattServerTest.kt
@@ -18,6 +18,7 @@
 
 import android.bluetooth.BluetoothAdapter
 import android.bluetooth.BluetoothDevice as FwkDevice
+import android.bluetooth.BluetoothGatt.GATT_SUCCESS
 import android.bluetooth.BluetoothGattCharacteristic as FwkCharacteristic
 import android.bluetooth.BluetoothGattServer
 import android.bluetooth.BluetoothGattServerCallback
@@ -110,7 +111,7 @@
             }
 
         bluetoothLe.openGattServer(listOf()) {
-            connectRequest.first().accept {}
+            connectRequests.first().accept {}
         }
 
         Assert.assertTrue(opened.isCompleted)
@@ -136,7 +137,7 @@
 
         launch {
             bluetoothLe.openGattServer(services) {
-                connectRequest.collect {
+                connectRequests.collect {
                     it.reject()
                     Assert.assertThrows(IllegalStateException::class.java) {
                         runBlocking {
@@ -171,7 +172,7 @@
 
         launch {
             bluetoothLe.openGattServer(services) {
-                connectRequest.collect {
+                connectRequests.collect {
                     it.accept {}
                     Assert.assertThrows(IllegalStateException::class.java) {
                         it.reject()
@@ -205,11 +206,11 @@
 
         launch {
             bluetoothLe.openGattServer(services) {
-                connectRequest.collect {
+                connectRequests.collect {
                     it.accept {
                         when (val request = requests.first()) {
-                            is GattServerRequest.ReadCharacteristicRequest -> {
-                                request.sendResponse(true, valueToRead.toByteArray())
+                            is GattServerRequest.ReadCharacteristic -> {
+                                request.sendResponse(valueToRead.toByteArray())
                             }
                             else -> fail("unexpected request")
                         }
@@ -227,6 +228,53 @@
     }
 
     @Test
+    fun readCharacteristic_sendFailure() = runTest {
+        val services = listOf(service1, service2)
+        val device = createDevice("00:11:22:33:44:55")
+        val closed = CompletableDeferred<Unit>()
+        val responsed = CompletableDeferred<Unit>()
+
+        runAfterServicesAreAdded(services.size) {
+            connectDevice(device) {
+                serverAdapter.callback.onCharacteristicReadRequest(
+                    device, /*requestId=*/1, /*offset=*/0, readCharacteristic.fwkCharacteristic)
+            }
+        }
+        serverAdapter.onCloseGattServerListener =
+            StubServerFrameworkAdapter.OnCloseGattServerListener {
+                closed.complete(Unit)
+            }
+        serverAdapter.onSendResponseListener =
+            StubServerFrameworkAdapter.OnSendResponseListener { _, requestId, status, _, value ->
+                Assert.assertEquals(1, requestId)
+                Assert.assertNotEquals(GATT_SUCCESS, status)
+                Assert.assertNull(value)
+                responsed.complete(Unit)
+            }
+
+        launch {
+            bluetoothLe.openGattServer(services) {
+                connectRequests.collect {
+                    it.accept {
+                        when (val request = requests.first()) {
+                            is GattServerRequest.ReadCharacteristic -> {
+                                request.sendFailure()
+                            }
+                            else -> fail("unexpected request")
+                        }
+                        // Close the server
+                        this@launch.cancel()
+                    }
+                }
+            }
+        }.join()
+
+        // Ensure if the server is closed
+        Assert.assertTrue(closed.isCompleted)
+        Assert.assertTrue(responsed.isCompleted)
+    }
+
+    @Test
     fun readUnknownCharacteristic_failsWithoutNotified() = runTest {
         val services = listOf(service1, service2)
         val device = createDevice("00:11:22:33:44:55")
@@ -248,12 +296,12 @@
 
         launch {
             bluetoothLe.openGattServer(services) {
-                connectRequest.collect {
+                connectRequests.collect {
                     it.accept {
                         when (val request = requests.first()) {
-                            is GattServerRequest.ReadCharacteristicRequest -> {
+                            is GattServerRequest.ReadCharacteristic -> {
                                 Assert.assertEquals(readCharacteristic, request.characteristic)
-                                request.sendResponse(true, valueToRead.toByteArray())
+                                request.sendResponse(valueToRead.toByteArray())
                             }
 
                             else -> fail("unexpected request")
@@ -289,12 +337,12 @@
 
         launch {
             bluetoothLe.openGattServer(services) {
-                connectRequest.collect {
+                connectRequests.collect {
                     it.accept {
                         when (val request = requests.first()) {
-                            is GattServerRequest.WriteCharacteristicRequest -> {
+                            is GattServerRequest.WriteCharacteristic -> {
                                 Assert.assertEquals(valueToWrite, request.value?.toInt())
-                                request.sendResponse(true, request.value)
+                                request.sendResponse(request.value)
                             }
 
                             else -> fail("unexpected request")
@@ -312,6 +360,57 @@
     }
 
     @Test
+    fun writeCharacteristic_sendFailure() = runTest {
+        val services = listOf(service1, service2)
+        val device = createDevice("00:11:22:33:44:55")
+        val closed = CompletableDeferred<Unit>()
+        val responsed = CompletableDeferred<Unit>()
+        val valueToWrite = 42
+
+        runAfterServicesAreAdded(services.size) {
+            connectDevice(device) {
+                serverAdapter.callback.onCharacteristicWriteRequest(
+                    device, /*requestId=*/1, writeCharacteristic.fwkCharacteristic,
+                    /*preparedWrite=*/false, /*responseNeeded=*/false,
+                    /*offset=*/0, valueToWrite.toByteArray()
+                )
+            }
+        }
+        serverAdapter.onCloseGattServerListener =
+            StubServerFrameworkAdapter.OnCloseGattServerListener {
+                closed.complete(Unit)
+            }
+        serverAdapter.onSendResponseListener =
+            StubServerFrameworkAdapter.OnSendResponseListener { _, requestId, status, _, value ->
+                Assert.assertEquals(1, requestId)
+                Assert.assertNotEquals(GATT_SUCCESS, status)
+                Assert.assertNull(value)
+                responsed.complete(Unit)
+            }
+
+        launch {
+            bluetoothLe.openGattServer(services) {
+                connectRequests.collect {
+                    it.accept {
+                        when (val request = requests.first()) {
+                            is GattServerRequest.WriteCharacteristic -> {
+                                Assert.assertEquals(valueToWrite, request.value?.toInt())
+                                request.sendFailure()
+                            }
+
+                            else -> fail("unexpected request")
+                        }
+                        // Close the server
+                        this@launch.cancel()
+                    }
+                }
+            }
+        }.join()
+
+        Assert.assertTrue(closed.isCompleted)
+    }
+
+    @Test
     fun notifyCharacteristic() = runTest {
        val services = listOf(service1, service2)
         val device = createDevice("00:11:22:33:44:55")
@@ -337,7 +436,7 @@
 
         launch {
             bluetoothLe.openGattServer(services) {
-                connectRequest.collect {
+                connectRequests.collect {
                     it.accept {
                         notify(notifyCharacteristic, valueToNotify.toByteArray())
                         // Close the server
@@ -370,7 +469,7 @@
         launch {
             bluetoothLe.openGattServer(listOf(service1)) {
                 updateServices(listOf(service2))
-                connectRequest.first().accept {}
+                connectRequests.first().accept {}
             }
         }.join()
 
@@ -413,6 +512,7 @@
         var onCloseGattServerListener: OnCloseGattServerListener? = null
         var onAddServiceListener: OnAddServiceListener? = null
         var onNotifyCharacteristicChangedListener: OnNotifyCharacteristicChangedListener? = null
+        var onSendResponseListener: OnSendResponseListener? = null
 
         override fun openGattServer(context: Context, callback: BluetoothGattServerCallback) {
             baseAdapter.openGattServer(context, callback)
@@ -452,6 +552,8 @@
             value: ByteArray?
         ) {
             baseAdapter.sendResponse(device, requestId, status, offset, value)
+            onSendResponseListener
+                ?.onSendResponse(device, requestId, status, offset, value)
         }
 
         fun interface OnOpenGattServerListener {
@@ -463,6 +565,15 @@
         fun interface OnCloseGattServerListener {
             fun onCloseGattServer()
         }
+        fun interface OnSendResponseListener {
+            fun onSendResponse(
+                device: FwkDevice,
+                requestId: Int,
+                status: Int,
+                offset: Int,
+                value: ByteArray?
+            )
+        }
         fun interface OnNotifyCharacteristicChangedListener {
             fun onNotifyCharacteristicChanged(
                 device: FwkDevice,
diff --git a/bluetooth/bluetooth/api/current.txt b/bluetooth/bluetooth/api/current.txt
index 2bfa8e7..65b4cb4 100644
--- a/bluetooth/bluetooth/api/current.txt
+++ b/bluetooth/bluetooth/api/current.txt
@@ -2,15 +2,16 @@
 package androidx.bluetooth {
 
   public final class AdvertiseParams {
-    ctor public AdvertiseParams(optional boolean shouldIncludeDeviceAddress, optional boolean shouldIncludeDeviceName, optional boolean isConnectable, optional boolean isDiscoverable, optional int timeoutMillis, optional java.util.Map<java.lang.Integer,byte[]> manufacturerData, optional java.util.Map<java.util.UUID,byte[]> serviceData, optional java.util.List<java.util.UUID> serviceUuids);
+    ctor public AdvertiseParams(optional boolean shouldIncludeDeviceAddress, optional boolean shouldIncludeDeviceName, optional boolean isConnectable, optional boolean isDiscoverable, optional @IntRange(from=0L, to=655350L) int durationMillis, optional java.util.Map<java.lang.Integer,byte[]> manufacturerData, optional java.util.Map<java.util.UUID,byte[]> serviceData, optional java.util.List<java.util.UUID> serviceUuids);
+    method public int getDurationMillis();
     method public java.util.Map<java.lang.Integer,byte[]> getManufacturerData();
     method public java.util.Map<java.util.UUID,byte[]> getServiceData();
     method public java.util.List<java.util.UUID> getServiceUuids();
     method public boolean getShouldIncludeDeviceAddress();
     method public boolean getShouldIncludeDeviceName();
-    method public int getTimeoutMillis();
     method public boolean isConnectable();
     method public boolean isDiscoverable();
+    property public final int durationMillis;
     property public final boolean isConnectable;
     property public final boolean isDiscoverable;
     property public final java.util.Map<java.lang.Integer,byte[]> manufacturerData;
@@ -18,27 +19,21 @@
     property public final java.util.List<java.util.UUID> serviceUuids;
     property public final boolean shouldIncludeDeviceAddress;
     property public final boolean shouldIncludeDeviceName;
-    property public final int timeoutMillis;
   }
 
   public final class AdvertiseResult {
-    ctor public AdvertiseResult();
     field public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 102; // 0x66
     field public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 103; // 0x67
     field public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 104; // 0x68
     field public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 105; // 0x69
     field public static final int ADVERTISE_STARTED = 101; // 0x65
-    field public static final androidx.bluetooth.AdvertiseResult.Companion Companion;
-  }
-
-  public static final class AdvertiseResult.Companion {
+    field public static final androidx.bluetooth.AdvertiseResult INSTANCE;
   }
 
   public final class BluetoothAddress {
     ctor public BluetoothAddress(String address, int addressType);
     method public String getAddress();
     method public int getAddressType();
-    method public void setAddressType(int);
     property public final String address;
     property public final int addressType;
     field public static final int ADDRESS_TYPE_PUBLIC = 0; // 0x0
@@ -63,19 +58,21 @@
 
   public final class BluetoothLe {
     ctor public BluetoothLe(android.content.Context context);
-    method @RequiresPermission("android.permission.BLUETOOTH_ADVERTISE") public kotlinx.coroutines.flow.Flow<java.lang.Integer> advertise(androidx.bluetooth.AdvertiseParams advertiseParams);
-    method @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public suspend <R> Object? connectGatt(androidx.bluetooth.BluetoothDevice device, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattClientScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super kotlin.Result<? extends R>>);
-    method public suspend <R> Object? openGattServer(java.util.List<androidx.bluetooth.GattService> services, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattServerConnectScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super kotlin.Result<? extends R>>);
+    method @RequiresPermission("android.permission.BLUETOOTH_ADVERTISE") public suspend Object? advertise(androidx.bluetooth.AdvertiseParams advertiseParams, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>? block, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public suspend <R> Object? connectGatt(androidx.bluetooth.BluetoothDevice device, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattClientScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
+    method public suspend <R> Object? openGattServer(java.util.List<androidx.bluetooth.GattService> services, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattServerConnectScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
     method @RequiresPermission("android.permission.BLUETOOTH_SCAN") public kotlinx.coroutines.flow.Flow<androidx.bluetooth.ScanResult> scan(optional java.util.List<androidx.bluetooth.ScanFilter> filters);
   }
 
   public static interface BluetoothLe.GattClientScope {
-    method public suspend Object? awaitClose(kotlin.jvm.functions.Function0<kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public androidx.bluetooth.GattService? getService(java.util.UUID uuid);
-    method public java.util.List<androidx.bluetooth.GattService> getServices();
+    method public default java.util.List<androidx.bluetooth.GattService> getServices();
+    method public kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.bluetooth.GattService>> getServicesFlow();
     method public suspend Object? readCharacteristic(androidx.bluetooth.GattCharacteristic characteristic, kotlin.coroutines.Continuation<? super kotlin.Result<? extends byte[]>>);
     method public kotlinx.coroutines.flow.Flow<byte[]> subscribeToCharacteristic(androidx.bluetooth.GattCharacteristic characteristic);
     method public suspend Object? writeCharacteristic(androidx.bluetooth.GattCharacteristic characteristic, byte[] value, kotlin.coroutines.Continuation<? super kotlin.Result<? extends kotlin.Unit>>);
+    property public default java.util.List<androidx.bluetooth.GattService> services;
+    property public abstract kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.bluetooth.GattService>> servicesFlow;
   }
 
   public static final class BluetoothLe.GattServerConnectRequest {
@@ -86,9 +83,9 @@
   }
 
   public static interface BluetoothLe.GattServerConnectScope {
-    method public kotlinx.coroutines.flow.Flow<androidx.bluetooth.BluetoothLe.GattServerConnectRequest> getConnectRequest();
+    method public kotlinx.coroutines.flow.Flow<androidx.bluetooth.BluetoothLe.GattServerConnectRequest> getConnectRequests();
     method public void updateServices(java.util.List<androidx.bluetooth.GattService> services);
-    property public abstract kotlinx.coroutines.flow.Flow<androidx.bluetooth.BluetoothLe.GattServerConnectRequest> connectRequest;
+    property public abstract kotlinx.coroutines.flow.Flow<androidx.bluetooth.BluetoothLe.GattServerConnectRequest> connectRequests;
   }
 
   public static interface BluetoothLe.GattServerSessionScope {
@@ -119,19 +116,21 @@
   public static final class GattCharacteristic.Companion {
   }
 
-  public interface GattServerRequest {
+  public class GattServerRequest {
   }
 
-  public static final class GattServerRequest.ReadCharacteristicRequest implements androidx.bluetooth.GattServerRequest {
+  public static final class GattServerRequest.ReadCharacteristic extends androidx.bluetooth.GattServerRequest {
     method public androidx.bluetooth.GattCharacteristic getCharacteristic();
-    method public void sendResponse(boolean success, byte[]? value);
+    method public void sendFailure();
+    method public void sendResponse(byte[] value);
     property public final androidx.bluetooth.GattCharacteristic characteristic;
   }
 
-  public static final class GattServerRequest.WriteCharacteristicRequest implements androidx.bluetooth.GattServerRequest {
+  public static final class GattServerRequest.WriteCharacteristic extends androidx.bluetooth.GattServerRequest {
     method public androidx.bluetooth.GattCharacteristic getCharacteristic();
     method public byte[]? getValue();
-    method public void sendResponse(boolean success, byte[]? value);
+    method public void sendFailure();
+    method public void sendResponse(byte[]? value);
     property public final androidx.bluetooth.GattCharacteristic characteristic;
     property public final byte[]? value;
   }
diff --git a/bluetooth/bluetooth/api/restricted_current.txt b/bluetooth/bluetooth/api/restricted_current.txt
index 2bfa8e7..65b4cb4 100644
--- a/bluetooth/bluetooth/api/restricted_current.txt
+++ b/bluetooth/bluetooth/api/restricted_current.txt
@@ -2,15 +2,16 @@
 package androidx.bluetooth {
 
   public final class AdvertiseParams {
-    ctor public AdvertiseParams(optional boolean shouldIncludeDeviceAddress, optional boolean shouldIncludeDeviceName, optional boolean isConnectable, optional boolean isDiscoverable, optional int timeoutMillis, optional java.util.Map<java.lang.Integer,byte[]> manufacturerData, optional java.util.Map<java.util.UUID,byte[]> serviceData, optional java.util.List<java.util.UUID> serviceUuids);
+    ctor public AdvertiseParams(optional boolean shouldIncludeDeviceAddress, optional boolean shouldIncludeDeviceName, optional boolean isConnectable, optional boolean isDiscoverable, optional @IntRange(from=0L, to=655350L) int durationMillis, optional java.util.Map<java.lang.Integer,byte[]> manufacturerData, optional java.util.Map<java.util.UUID,byte[]> serviceData, optional java.util.List<java.util.UUID> serviceUuids);
+    method public int getDurationMillis();
     method public java.util.Map<java.lang.Integer,byte[]> getManufacturerData();
     method public java.util.Map<java.util.UUID,byte[]> getServiceData();
     method public java.util.List<java.util.UUID> getServiceUuids();
     method public boolean getShouldIncludeDeviceAddress();
     method public boolean getShouldIncludeDeviceName();
-    method public int getTimeoutMillis();
     method public boolean isConnectable();
     method public boolean isDiscoverable();
+    property public final int durationMillis;
     property public final boolean isConnectable;
     property public final boolean isDiscoverable;
     property public final java.util.Map<java.lang.Integer,byte[]> manufacturerData;
@@ -18,27 +19,21 @@
     property public final java.util.List<java.util.UUID> serviceUuids;
     property public final boolean shouldIncludeDeviceAddress;
     property public final boolean shouldIncludeDeviceName;
-    property public final int timeoutMillis;
   }
 
   public final class AdvertiseResult {
-    ctor public AdvertiseResult();
     field public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 102; // 0x66
     field public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 103; // 0x67
     field public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 104; // 0x68
     field public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 105; // 0x69
     field public static final int ADVERTISE_STARTED = 101; // 0x65
-    field public static final androidx.bluetooth.AdvertiseResult.Companion Companion;
-  }
-
-  public static final class AdvertiseResult.Companion {
+    field public static final androidx.bluetooth.AdvertiseResult INSTANCE;
   }
 
   public final class BluetoothAddress {
     ctor public BluetoothAddress(String address, int addressType);
     method public String getAddress();
     method public int getAddressType();
-    method public void setAddressType(int);
     property public final String address;
     property public final int addressType;
     field public static final int ADDRESS_TYPE_PUBLIC = 0; // 0x0
@@ -63,19 +58,21 @@
 
   public final class BluetoothLe {
     ctor public BluetoothLe(android.content.Context context);
-    method @RequiresPermission("android.permission.BLUETOOTH_ADVERTISE") public kotlinx.coroutines.flow.Flow<java.lang.Integer> advertise(androidx.bluetooth.AdvertiseParams advertiseParams);
-    method @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public suspend <R> Object? connectGatt(androidx.bluetooth.BluetoothDevice device, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattClientScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super kotlin.Result<? extends R>>);
-    method public suspend <R> Object? openGattServer(java.util.List<androidx.bluetooth.GattService> services, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattServerConnectScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super kotlin.Result<? extends R>>);
+    method @RequiresPermission("android.permission.BLUETOOTH_ADVERTISE") public suspend Object? advertise(androidx.bluetooth.AdvertiseParams advertiseParams, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>? block, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public suspend <R> Object? connectGatt(androidx.bluetooth.BluetoothDevice device, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattClientScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
+    method public suspend <R> Object? openGattServer(java.util.List<androidx.bluetooth.GattService> services, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattServerConnectScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
     method @RequiresPermission("android.permission.BLUETOOTH_SCAN") public kotlinx.coroutines.flow.Flow<androidx.bluetooth.ScanResult> scan(optional java.util.List<androidx.bluetooth.ScanFilter> filters);
   }
 
   public static interface BluetoothLe.GattClientScope {
-    method public suspend Object? awaitClose(kotlin.jvm.functions.Function0<kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public androidx.bluetooth.GattService? getService(java.util.UUID uuid);
-    method public java.util.List<androidx.bluetooth.GattService> getServices();
+    method public default java.util.List<androidx.bluetooth.GattService> getServices();
+    method public kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.bluetooth.GattService>> getServicesFlow();
     method public suspend Object? readCharacteristic(androidx.bluetooth.GattCharacteristic characteristic, kotlin.coroutines.Continuation<? super kotlin.Result<? extends byte[]>>);
     method public kotlinx.coroutines.flow.Flow<byte[]> subscribeToCharacteristic(androidx.bluetooth.GattCharacteristic characteristic);
     method public suspend Object? writeCharacteristic(androidx.bluetooth.GattCharacteristic characteristic, byte[] value, kotlin.coroutines.Continuation<? super kotlin.Result<? extends kotlin.Unit>>);
+    property public default java.util.List<androidx.bluetooth.GattService> services;
+    property public abstract kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.bluetooth.GattService>> servicesFlow;
   }
 
   public static final class BluetoothLe.GattServerConnectRequest {
@@ -86,9 +83,9 @@
   }
 
   public static interface BluetoothLe.GattServerConnectScope {
-    method public kotlinx.coroutines.flow.Flow<androidx.bluetooth.BluetoothLe.GattServerConnectRequest> getConnectRequest();
+    method public kotlinx.coroutines.flow.Flow<androidx.bluetooth.BluetoothLe.GattServerConnectRequest> getConnectRequests();
     method public void updateServices(java.util.List<androidx.bluetooth.GattService> services);
-    property public abstract kotlinx.coroutines.flow.Flow<androidx.bluetooth.BluetoothLe.GattServerConnectRequest> connectRequest;
+    property public abstract kotlinx.coroutines.flow.Flow<androidx.bluetooth.BluetoothLe.GattServerConnectRequest> connectRequests;
   }
 
   public static interface BluetoothLe.GattServerSessionScope {
@@ -119,19 +116,21 @@
   public static final class GattCharacteristic.Companion {
   }
 
-  public interface GattServerRequest {
+  public class GattServerRequest {
   }
 
-  public static final class GattServerRequest.ReadCharacteristicRequest implements androidx.bluetooth.GattServerRequest {
+  public static final class GattServerRequest.ReadCharacteristic extends androidx.bluetooth.GattServerRequest {
     method public androidx.bluetooth.GattCharacteristic getCharacteristic();
-    method public void sendResponse(boolean success, byte[]? value);
+    method public void sendFailure();
+    method public void sendResponse(byte[] value);
     property public final androidx.bluetooth.GattCharacteristic characteristic;
   }
 
-  public static final class GattServerRequest.WriteCharacteristicRequest implements androidx.bluetooth.GattServerRequest {
+  public static final class GattServerRequest.WriteCharacteristic extends androidx.bluetooth.GattServerRequest {
     method public androidx.bluetooth.GattCharacteristic getCharacteristic();
     method public byte[]? getValue();
-    method public void sendResponse(boolean success, byte[]? value);
+    method public void sendFailure();
+    method public void sendResponse(byte[]? value);
     property public final androidx.bluetooth.GattCharacteristic characteristic;
     property public final byte[]? value;
   }
diff --git a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/AdvertiseParamsTest.kt b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/AdvertiseParamsTest.kt
index 6b59576..dc7d197 100644
--- a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/AdvertiseParamsTest.kt
+++ b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/AdvertiseParamsTest.kt
@@ -33,7 +33,7 @@
         assertEquals(false, advertiseParams.shouldIncludeDeviceName)
         assertEquals(false, advertiseParams.isConnectable)
         assertEquals(false, advertiseParams.isDiscoverable)
-        assertEquals(0, advertiseParams.timeoutMillis)
+        assertEquals(0, advertiseParams.durationMillis)
         assertEquals(0, advertiseParams.manufacturerData.size)
         assertEquals(0, advertiseParams.serviceData.size)
         assertEquals(0, advertiseParams.serviceUuids.size)
diff --git a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothAddressTest.kt b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothAddressTest.kt
index beaa6f1..3c0e6e4 100644
--- a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothAddressTest.kt
+++ b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothAddressTest.kt
@@ -18,6 +18,7 @@
 
 import junit.framework.TestCase.assertEquals
 import kotlin.test.assertFailsWith
+import org.junit.Assert.assertTrue
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -66,10 +67,9 @@
     fun constructorWithInvalidAddressType() {
         val invalidAddressType = -1
 
-        val bluetoothAddress = BluetoothAddress(TEST_ADDRESS_UNKNOWN, invalidAddressType)
+        val result = runCatching { BluetoothAddress(TEST_ADDRESS_UNKNOWN, invalidAddressType) }
 
-        assertEquals(TEST_ADDRESS_UNKNOWN, bluetoothAddress.address)
-        assertEquals(BluetoothAddress.ADDRESS_TYPE_UNKNOWN, bluetoothAddress.addressType)
+        assertTrue(result.exceptionOrNull() is IllegalArgumentException)
     }
 
     @Test
diff --git a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothLeTest.kt b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothLeTest.kt
index 2b6b036..822fd61 100644
--- a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothLeTest.kt
+++ b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothLeTest.kt
@@ -24,7 +24,6 @@
 import java.util.UUID
 import junit.framework.TestCase.assertEquals
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.test.runTest
 import org.junit.Assume
 import org.junit.Before
@@ -71,10 +70,9 @@
     fun advertise() = runTest {
         val advertiseParams = AdvertiseParams()
 
-        val advertiseResultStarted = bluetoothLe.advertise(advertiseParams)
-            .first()
-
-        assertEquals(AdvertiseResult.ADVERTISE_STARTED, advertiseResultStarted)
+        bluetoothLe.advertise(advertiseParams) {
+            assertEquals(AdvertiseResult.ADVERTISE_STARTED, it)
+        }
     }
 
     @Test
@@ -86,9 +84,8 @@
             serviceData = mapOf(parcelUuid to serviceData)
         )
 
-        val advertiseResultStarted = bluetoothLe.advertise(advertiseParams)
-            .first()
-
-        assertEquals(AdvertiseResult.ADVERTISE_FAILED_DATA_TOO_LARGE, advertiseResultStarted)
+        bluetoothLe.advertise(advertiseParams) {
+            assertEquals(AdvertiseResult.ADVERTISE_FAILED_DATA_TOO_LARGE, it)
+        }
     }
 }
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/AdvertiseParams.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/AdvertiseParams.kt
index f45a905..a8a258b 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/AdvertiseParams.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/AdvertiseParams.kt
@@ -16,11 +16,11 @@
 
 package androidx.bluetooth
 
+import androidx.annotation.IntRange
 import java.util.UUID
 
 /**
- * A single class to provide a way to adjust advertising preferences and advertise data packet.
- *
+ * A class to provide a way to adjust advertising preferences and advertise data packet.
  */
 class AdvertiseParams(
     /* Whether the device address will be included in the advertisement packet. */
@@ -33,32 +33,35 @@
     val shouldIncludeDeviceName: Boolean = false,
     /* Whether the advertisement will indicate connectable. */
     val isConnectable: Boolean = false,
-    /* Whether the advertisement will be discoverable. */
-    val isDiscoverable: Boolean = false,
-    /* Advertising time limit in milliseconds. */
-    val timeoutMillis: Int = 0,
     /**
-     * A map of manufacturer specific data.
+     * Whether the advertisement will be discoverable.
+     *
+     * Please note that it would be ignored under API level 34 and [isConnectable] would be
+     * used instead.
+     */
+    val isDiscoverable: Boolean = false,
+    /**
+     * Advertising duration in milliseconds
+     *
+     * It must not exceed 655350 milliseconds. A value of 0 means advertising continues
+     * until it is stopped explicitly.
+     */
+    @IntRange(from = 0, to = 655350) val durationMillis: Int = 0,
+
+    /**
+     * A map of company identifiers to manufacturer specific data.
      * <p>
      * Please refer to the Bluetooth Assigned Numbers document provided by the <a
-     * href="https://www.bluetooth.org">Bluetooth SIG</a> for a list of existing company
+     * href="https://www.bluetooth.org">Bluetooth SIG</a> for the list of existing company
      * identifiers.
-     *
-     * Map<Int> Manufacturer ID assigned by Bluetooth SIG.
-     * Map<ByteArray> Manufacturer specific data
      */
     val manufacturerData: Map<Int, ByteArray> = emptyMap(),
     /**
-     * A map of service data to advertise data.
-     *
-     * UUID 16-bit UUID of the service the data is associated with
-     * ByteArray serviceData Service data
+     * A map of 16-bit UUIDs of the services to corresponding additional service data.
      */
     val serviceData: Map<UUID, ByteArray> = emptyMap(),
     /**
-     * A list of service UUID to advertise data.
-     *
-     * UUID A service UUID to be advertised.
+     * A list of service UUIDs to advertise.
      */
     val serviceUuids: List<UUID> = emptyList()
 )
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/AdvertiseResult.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/AdvertiseResult.kt
index 69f8687..304bdd7 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/AdvertiseResult.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/AdvertiseResult.kt
@@ -24,7 +24,7 @@
  * An advertise result indicates the result of a request to start advertising, whether success
  * or failure.
  */
-class AdvertiseResult {
+object AdvertiseResult {
     @Target(AnnotationTarget.TYPE)
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     @Retention(AnnotationRetention.SOURCE)
@@ -37,20 +37,18 @@
     )
     annotation class ResultType
 
-    companion object {
-        /** Advertise started successfully. */
-        const val ADVERTISE_STARTED: Int = 101
+    /** Advertise started successfully. */
+    const val ADVERTISE_STARTED: Int = 101
 
-        /** Advertise failed to start because the data is too large. */
-        const val ADVERTISE_FAILED_DATA_TOO_LARGE: Int = 102
+    /** Advertise failed to start because the data is too large. */
+    const val ADVERTISE_FAILED_DATA_TOO_LARGE: Int = 102
 
-        /** Advertise failed to start because the advertise feature is not supported. */
-        const val ADVERTISE_FAILED_FEATURE_UNSUPPORTED: Int = 103
+    /** Advertise failed to start because the advertise feature is not supported. */
+    const val ADVERTISE_FAILED_FEATURE_UNSUPPORTED: Int = 103
 
-        /** Advertise failed to start because of an internal error. */
-        const val ADVERTISE_FAILED_INTERNAL_ERROR: Int = 104
+    /** Advertise failed to start because of an internal error. */
+    const val ADVERTISE_FAILED_INTERNAL_ERROR: Int = 104
 
-        /** Advertise failed to start because of too many advertisers. */
-        const val ADVERTISE_FAILED_TOO_MANY_ADVERTISERS: Int = 105
-    }
+    /** Advertise failed to start because of too many advertisers. */
+    const val ADVERTISE_FAILED_TOO_MANY_ADVERTISERS: Int = 105
 }
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt
index 3c272a0..ecbca45 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt
@@ -24,11 +24,10 @@
 /**
  * Represents a Bluetooth address for a remote device.
  *
- * @property address valid Bluetooth MAC address
- * @property addressType valid address type
- *
+ * @property address a valid Bluetooth MAC address
+ * @property addressType a valid address type
  */
-class BluetoothAddress(val address: String, @AddressType var addressType: Int) {
+class BluetoothAddress(val address: String, @AddressType val addressType: Int) {
     companion object {
         /** Address type is public and registered with the IEEE. */
         const val ADDRESS_TYPE_PUBLIC: Int = 0
@@ -61,12 +60,13 @@
             throw IllegalArgumentException("$address is not a valid Bluetooth address")
         }
 
-        addressType = when (addressType) {
+        when (addressType) {
             ADDRESS_TYPE_PUBLIC,
             ADDRESS_TYPE_RANDOM_STATIC,
             ADDRESS_TYPE_RANDOM_RESOLVABLE,
-            ADDRESS_TYPE_RANDOM_NON_RESOLVABLE -> addressType
-            else -> ADDRESS_TYPE_UNKNOWN
+            ADDRESS_TYPE_RANDOM_NON_RESOLVABLE,
+            ADDRESS_TYPE_UNKNOWN -> Unit
+            else -> throw IllegalArgumentException("$addressType is not a valid address type")
         }
     }
 
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
index e59357f..c4dcf2c 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
@@ -25,16 +25,26 @@
 import android.bluetooth.le.ScanResult as FwkScanResult
 import android.bluetooth.le.ScanSettings
 import android.content.Context
+import android.os.Build
 import android.os.ParcelUuid
 import android.util.Log
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
 import androidx.annotation.RequiresPermission
 import androidx.annotation.RestrictTo
 import androidx.annotation.VisibleForTesting
 import java.util.UUID
+import kotlin.coroutines.coroutineContext
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.job
 
 /**
  * Entry point for BLE related operations. This class provides a way to perform Bluetooth LE
@@ -46,6 +56,19 @@
         private const val TAG = "BluetoothLe"
     }
 
+    @RequiresApi(34)
+    private object BluetoothLeApi34Impl {
+        @JvmStatic
+        @DoNotInline
+        fun setDiscoverable(
+            builder: AdvertiseSettings.Builder,
+            isDiscoverable: Boolean
+        ): AdvertiseSettings.Builder {
+            builder.setDiscoverable(isDiscoverable)
+            return builder
+        }
+    }
+
     private val bluetoothManager =
         context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?
     private val bluetoothAdapter = bluetoothManager?.adapter
@@ -67,39 +90,44 @@
     var onStartScanListener: OnStartScanListener? = null
 
     /**
-     * Returns a _cold_ [Flow] to start Bluetooth LE Advertising.
-     * When the flow is successfully collected, the operation status [AdvertiseResult] will be
-     * delivered via the flow [kotlinx.coroutines.channels.Channel].
+     * Starts Bluetooth LE advertising
      *
-     * @param advertiseParams [AdvertiseParams] for Bluetooth LE advertising
-     * @return a _cold_ [Flow] with [AdvertiseResult] status in the data stream
+     * Note that this method may not complete if the duration is set to 0.
+     * To stop advertising, in that case, you should cancel the coroutine.
+     *
+     * @param advertiseParams [AdvertiseParams] for Bluetooth LE advertising.
+     * @param block an optional block of code that is invoked when advertising is started or failed.
+     *
+     * @throws IllegalArgumentException if the advertise parameters are not valid.
      */
     @RequiresPermission("android.permission.BLUETOOTH_ADVERTISE")
-    fun advertise(advertiseParams: AdvertiseParams): Flow<@AdvertiseResult.ResultType Int> =
-        callbackFlow {
+    suspend fun advertise(
+        advertiseParams: AdvertiseParams,
+        block: (suspend (@AdvertiseResult.ResultType Int) -> Unit)? = null
+    ) {
+        val result = CompletableDeferred<Int>()
+
         val callback = object : AdvertiseCallback() {
             override fun onStartFailure(errorCode: Int) {
                 Log.d(TAG, "onStartFailure() called with: errorCode = $errorCode")
 
                 when (errorCode) {
                     ADVERTISE_FAILED_DATA_TOO_LARGE ->
-                        trySend(AdvertiseResult.ADVERTISE_FAILED_DATA_TOO_LARGE)
+                        result.complete(AdvertiseResult.ADVERTISE_FAILED_DATA_TOO_LARGE)
 
                     ADVERTISE_FAILED_FEATURE_UNSUPPORTED ->
-                        trySend(AdvertiseResult.ADVERTISE_FAILED_FEATURE_UNSUPPORTED)
+                        result.complete(AdvertiseResult.ADVERTISE_FAILED_FEATURE_UNSUPPORTED)
 
                     ADVERTISE_FAILED_INTERNAL_ERROR ->
-                        trySend(AdvertiseResult.ADVERTISE_FAILED_INTERNAL_ERROR)
+                        result.complete(AdvertiseResult.ADVERTISE_FAILED_INTERNAL_ERROR)
 
                     ADVERTISE_FAILED_TOO_MANY_ADVERTISERS ->
-                        trySend(AdvertiseResult.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS)
+                        result.complete(AdvertiseResult.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS)
                 }
             }
 
             override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
-                Log.d(TAG, "onStartSuccess() called with: settingsInEffect = $settingsInEffect")
-
-                trySend(AdvertiseResult.ADVERTISE_STARTED)
+                result.complete(AdvertiseResult.ADVERTISE_STARTED)
             }
         }
 
@@ -107,9 +135,14 @@
 
         val advertiseSettings = with(AdvertiseSettings.Builder()) {
             setConnectable(advertiseParams.isConnectable)
-            setTimeout(advertiseParams.timeoutMillis)
-            // TODO(b/290697177) Add when AndroidX is targeting Android U
-//            setDiscoverable(advertiseParams.isDiscoverable)
+            advertiseParams.durationMillis.let {
+                if (it !in 0..655350)
+                    throw IllegalArgumentException("advertise duration must be in [0, 655350]")
+                setTimeout(it)
+            }
+            if (Build.VERSION.SDK_INT >= 34) {
+                BluetoothLeApi34Impl.setDiscoverable(this, advertiseParams.isDiscoverable)
+            }
             build()
         }
 
@@ -127,13 +160,21 @@
             build()
         }
 
-        Log.d(TAG, "bleAdvertiser.startAdvertising($advertiseSettings, $advertiseData) called")
         bleAdvertiser?.startAdvertising(advertiseSettings, advertiseData, callback)
 
-        awaitClose {
-            Log.d(TAG, "bleAdvertiser.stopAdvertising() called")
+        coroutineContext.job.invokeOnCompletion {
             bleAdvertiser?.stopAdvertising(callback)
         }
+        result.await().let {
+            block?.invoke(it)
+            if (it == AdvertiseResult.ADVERTISE_STARTED) {
+                if (advertiseParams.durationMillis > 0) {
+                    delay(advertiseParams.durationMillis.toLong())
+                } else {
+                    awaitCancellation()
+                }
+            }
+        }
     }
 
     /**
@@ -176,9 +217,20 @@
     interface GattClientScope {
 
         /**
-         * Gets the services discovered from the remote device.
+         * A flow of GATT services discovered from the remote device.
+         *
+         * If the services of the remote device has changed, the new services will be
+         * discovered and emitted automatically.
          */
-        fun getServices(): List<GattService>
+        val servicesFlow: StateFlow<List<GattService>>
+
+        /**
+         * GATT services recently discovered from the remote device.
+         *
+         * Note that this can be changed, subscribe to [servicesFlow] to get notified
+         * of services changes.
+         */
+        val services: List<GattService> get() = servicesFlow.value
 
         /**
          * Gets the service of the remote device by UUID.
@@ -213,12 +265,6 @@
          * Returns a _cold_ [Flow] that contains the indicated value of the given characteristic.
          */
         fun subscribeToCharacteristic(characteristic: GattCharacteristic): Flow<ByteArray>
-
-        /**
-         * Suspends the current coroutine until the pending operations are handled and the
-         * connection is closed, then it invokes the given [block] before resuming the coroutine.
-         */
-        suspend fun awaitClose(block: () -> Unit)
     }
 
     /**
@@ -230,6 +276,7 @@
      * @param device a [BluetoothDevice] to connect to
      * @param block a block of code that is invoked after the connection is made
      *
+     * @throws CancellationException if connect failed or it's canceled
      * @return a result returned by the given block if the connection was successfully finished
      *         or a failure with the corresponding reason
      *
@@ -238,14 +285,14 @@
     suspend fun <R> connectGatt(
         device: BluetoothDevice,
         block: suspend GattClientScope.() -> R
-    ): Result<R> {
+    ): R {
         return client.connect(device, block)
     }
 
     /**
      * A scope for handling connect requests from remote devices.
      *
-     * @property connectRequest connect requests from remote devices.
+     * @property connectRequests connect requests from remote devices.
      *
      * @see BluetoothLe#openGattServer
      */
@@ -253,7 +300,7 @@
         /**
          * A _hot_ flow of [GattServerConnectRequest].
          */
-        val connectRequest: Flow<GattServerConnectRequest>
+        val connectRequests: Flow<GattServerConnectRequest>
 
         /**
          * Updates the services of the opened GATT server.
@@ -281,8 +328,8 @@
         /**
          * A _hot_ [Flow] of incoming requests from the client.
          *
-         * A request is either [GattServerRequest.ReadCharacteristicRequest] or
-         * [GattServerRequest.WriteCharacteristicRequest]
+         * A request is either [GattServerRequest.ReadCharacteristic] or
+         * [GattServerRequest.WriteCharacteristic]
          */
         val requests: Flow<GattServerRequest>
 
@@ -342,7 +389,7 @@
     suspend fun <R> openGattServer(
         services: List<GattService>,
         block: suspend GattServerConnectScope.() -> R
-    ): Result<R> {
+    ): R {
         return server.open(services, block)
     }
 
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt
index 290e1a9..01456ac 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt
@@ -39,7 +39,10 @@
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.filter
@@ -48,6 +51,7 @@
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.withTimeout
 
 /**
  * A class for handling operations as a GATT client role.
@@ -88,6 +92,8 @@
          * The maximum ATT size(512) + header(3)
          */
         private const val GATT_MAX_MTU = 515
+
+        private const val CONNECT_TIMEOUT_MS = 30_000L
         private val CCCD_UID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
     }
 
@@ -131,20 +137,21 @@
     suspend fun <R> connect(
         device: BluetoothDevice,
         block: suspend BluetoothLe.GattClientScope.() -> R
-    ): Result<R> = coroutineScope {
+    ): R = coroutineScope {
         val connectResult = CompletableDeferred<Unit>(parent = coroutineContext.job)
         val callbackResultsFlow =
             MutableSharedFlow<CallbackResult>(extraBufferCapacity = Int.MAX_VALUE)
         val subscribeMap: MutableMap<FwkCharacteristic, SubscribeListener> = mutableMapOf()
         val subscribeMutex = Mutex()
         val attributeMap = AttributeMap()
+        val servicesFlow = MutableStateFlow<List<GattService>>(listOf())
 
         val callback = object : BluetoothGattCallback() {
             override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
                 if (newState == BluetoothGatt.STATE_CONNECTED) {
                     fwkAdapter.requestMtu(GATT_MAX_MTU)
                 } else {
-                    connectResult.cancel("connect failed")
+                    cancel("connect failed")
                 }
             }
 
@@ -152,14 +159,24 @@
                 if (status == BluetoothGatt.GATT_SUCCESS) {
                     fwkAdapter.discoverServices()
                 } else {
-                    connectResult.cancel("mtu request failed")
+                    cancel("mtu request failed")
                 }
             }
 
             override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
                 attributeMap.updateWithFrameworkServices(fwkAdapter.getServices())
                 if (status == BluetoothGatt.GATT_SUCCESS) connectResult.complete(Unit)
-                else connectResult.cancel("service discover failed")
+                else cancel("service discover failed")
+                servicesFlow.tryEmit(attributeMap.getServices())
+                if (connectResult.isActive) {
+                    if (status == BluetoothGatt.GATT_SUCCESS) connectResult.complete(Unit)
+                    else connectResult.cancel("service discover failed")
+                }
+            }
+
+            override fun onServiceChanged(gatt: BluetoothGatt) {
+                // TODO: under API 31, we have to subscribe to the service changed characteristic.
+                fwkAdapter.discoverServices()
             }
 
             override fun onCharacteristicRead(
@@ -219,13 +236,11 @@
             }
         }
         if (!fwkAdapter.connectGatt(context, device.fwkDevice, callback)) {
-            return@coroutineScope Result.failure(CancellationException("failed to connect"))
+            throw CancellationException("failed to connect")
         }
 
-        try {
+        withTimeout(CONNECT_TIMEOUT_MS) {
             connectResult.await()
-        } catch (e: Throwable) {
-            return@coroutineScope Result.failure(e)
         }
         val gattScope = object : BluetoothLe.GattClientScope {
             val taskMutex = Mutex()
@@ -235,9 +250,7 @@
                 }
             }
 
-            override fun getServices(): List<GattService> {
-                return attributeMap.getServices()
-            }
+            override val servicesFlow: StateFlow<List<GattService>> = servicesFlow.asStateFlow()
 
             override fun getService(uuid: UUID): GattService? {
                 return fwkAdapter.getService(uuid)?.let { attributeMap.fromFwkService(it) }
@@ -339,19 +352,6 @@
                 }
             }
 
-            override suspend fun awaitClose(block: () -> Unit) {
-                try {
-                    // Wait for queued tasks done
-                    taskMutex.withLock {
-                        subscribeMutex.withLock {
-                            subscribeMap.values.forEach { it.finish() }
-                        }
-                    }
-                } finally {
-                    block()
-                }
-            }
-
             private suspend fun registerSubscribeListener(
                 characteristic: FwkCharacteristic,
                 callback: SubscribeListener
@@ -373,11 +373,7 @@
                 }
             }
         }
-        try {
-            Result.success(gattScope.block())
-        } catch (e: CancellationException) {
-            Result.failure(e)
-        }
+        gattScope.block()
     }
 
     private suspend inline fun <reified R : CallbackResult> takeMatchingResult(
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServer.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServer.kt
index 6d2edf4..8e1c43c 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServer.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServer.kt
@@ -93,8 +93,8 @@
     suspend fun <R> open(
         services: List<GattService>,
         block: suspend BluetoothLe.GattServerConnectScope.() -> R
-    ): Result<R> {
-        return Result.success(createServerScope(services).block())
+    ): R {
+        return createServerScope(services).block()
     }
 
     private fun createServerScope(services: List<GattService>): BluetoothLe.GattServerConnectScope {
@@ -103,7 +103,7 @@
             // Should be accessed only from the callback thread
             private val sessions: MutableMap<FwkDevice, Session> = mutableMapOf()
 
-            override val connectRequest = callbackFlow {
+            override val connectRequests = callbackFlow {
                     attributeMap.updateWithServices(services)
                     val callback = object : BluetoothGattServerCallback() {
                         override fun onConnectionStateChange(
@@ -133,7 +133,7 @@
                             attributeMap.fromFwkCharacteristic(characteristic)?.let { char ->
                                 findActiveSessionWithDevice(device)?.run {
                                     requestChannel.trySend(
-                                        GattServerRequest.ReadCharacteristicRequest(
+                                        GattServerRequest.ReadCharacteristic(
                                             this, requestId, offset, char
                                         )
                                     )
@@ -159,7 +159,7 @@
                             attributeMap.fromFwkCharacteristic(characteristic)?.let {
                                 findActiveSessionWithDevice(device)?.run {
                                     requestChannel.trySend(
-                                        GattServerRequest.WriteCharacteristicRequest(
+                                        GattServerRequest.WriteCharacteristic(
                                             this,
                                             requestId,
                                             it,
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerRequest.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerRequest.kt
index 8dd6554..1b477a1 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerRequest.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerRequest.kt
@@ -19,40 +19,57 @@
 import android.bluetooth.BluetoothGatt.GATT_READ_NOT_PERMITTED
 import android.bluetooth.BluetoothGatt.GATT_SUCCESS
 import android.bluetooth.BluetoothGatt.GATT_WRITE_NOT_PERMITTED
+import java.util.concurrent.atomic.AtomicBoolean
 
 /**
  * Represents a request to be handled as a GATT server role.
  *
  * @see BluetoothLe.GattServerConnectRequest.accept
  */
-interface GattServerRequest {
+open class GattServerRequest private constructor() {
+    private val handled = AtomicBoolean(false)
+
+    internal inline fun handleRequest(block: () -> Unit) {
+        if (handled.compareAndSet(false, true)) {
+            block()
+        } else {
+            throw IllegalStateException("Request is already handled")
+        }
+    }
+
     /**
      * Represents a read characteristic request.
      *
      * @property characteristic a characteristic to read
      */
-    class ReadCharacteristicRequest internal constructor(
+    class ReadCharacteristic internal constructor(
         private val session: GattServer.Session,
         private val requestId: Int,
         private val offset: Int,
         val characteristic: GattCharacteristic
-    ) : GattServerRequest {
+    ) : GattServerRequest() {
         /**
          * Sends the result for the read request.
          *
-         * @param success true if the request was successful
-         * @param value a value of the characteristic or `null` if it failed.
+         * @param value a value of the characteristic
          */
-        fun sendResponse(success: Boolean, value: ByteArray?) {
-            val resValue: ByteArray? = if (offset == 0 || value == null) value
-            else if (value.size > offset) value.copyOfRange(offset, value.size - 1)
-            else ByteArray(0)
-            session.sendResponse(
-                requestId,
-                if (success) GATT_SUCCESS else GATT_READ_NOT_PERMITTED,
-                offset,
-                resValue
-            )
+        fun sendResponse(value: ByteArray) {
+            handleRequest {
+                val resValue: ByteArray = if (offset == 0) value
+                else if (value.size > offset) value.copyOfRange(offset, value.size - 1)
+                else if (value.size == offset) byteArrayOf()
+                else byteArrayOf()
+                session.sendResponse(requestId, GATT_SUCCESS, offset, resValue)
+            }
+        }
+
+        /**
+         * Notifies the failure for the read request.
+         */
+        fun sendFailure() {
+            handleRequest {
+                session.sendResponse(requestId, GATT_READ_NOT_PERMITTED, offset, null)
+            }
         }
     }
 
@@ -62,25 +79,30 @@
      * @property characteristic a characteristic to write
      * @property value a value to write
      */
-    class WriteCharacteristicRequest internal constructor(
+    class WriteCharacteristic internal constructor(
         private val session: GattServer.Session,
         private val requestId: Int,
         val characteristic: GattCharacteristic,
         val value: ByteArray?
-    ) : GattServerRequest {
+    ) : GattServerRequest() {
         /**
          * Sends the result for the write request.
          *
-         * @param success true if the request was successful
          * @param value an optional value that is written
          */
-        fun sendResponse(success: Boolean, value: ByteArray?) {
-            session.sendResponse(
-                requestId,
-                if (success) GATT_SUCCESS else GATT_WRITE_NOT_PERMITTED,
-                0,
-                value
-            )
+        fun sendResponse(value: ByteArray?) {
+            handleRequest {
+                session.sendResponse(requestId, GATT_SUCCESS, 0, value)
+            }
+        }
+
+        /**
+         * Notifies the failure for the write request.
+         */
+        fun sendFailure() {
+            handleRequest {
+                session.sendResponse(requestId, GATT_WRITE_NOT_PERMITTED, 0, null)
+            }
         }
     }
 }
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/advertiser/AdvertiserFragment.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/advertiser/AdvertiserFragment.kt
index 89a87ca..3e1623b 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/advertiser/AdvertiserFragment.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/advertiser/AdvertiserFragment.kt
@@ -325,32 +325,33 @@
         advertiseJob = advertiseScope.launch {
             isAdvertising = true
 
-            bluetoothLe.advertise(viewModel.advertiseParams)
-                .collect {
-                    Log.d(TAG, "AdvertiseResult collected: $it")
+            bluetoothLe.advertise(viewModel.advertiseParams) {
+                when (it) {
+                    AdvertiseResult.ADVERTISE_STARTED -> {
+                        toast("ADVERTISE_STARTED").show()
+                    }
 
-                    when (it) {
-                        AdvertiseResult.ADVERTISE_STARTED -> {
-                            toast("ADVERTISE_STARTED").show()
-                        }
-                        AdvertiseResult.ADVERTISE_FAILED_DATA_TOO_LARGE -> {
-                            isAdvertising = false
-                            toast("ADVERTISE_FAILED_DATA_TOO_LARGE").show()
-                        }
-                        AdvertiseResult.ADVERTISE_FAILED_FEATURE_UNSUPPORTED -> {
-                            isAdvertising = false
-                            toast("ADVERTISE_FAILED_FEATURE_UNSUPPORTED").show()
-                        }
-                        AdvertiseResult.ADVERTISE_FAILED_INTERNAL_ERROR -> {
-                            isAdvertising = false
-                            toast("ADVERTISE_FAILED_INTERNAL_ERROR").show()
-                        }
-                        AdvertiseResult.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS -> {
-                            isAdvertising = false
-                            toast("ADVERTISE_FAILED_TOO_MANY_ADVERTISERS").show()
-                        }
+                    AdvertiseResult.ADVERTISE_FAILED_DATA_TOO_LARGE -> {
+                        isAdvertising = false
+                        toast("ADVERTISE_FAILED_DATA_TOO_LARGE").show()
+                    }
+
+                    AdvertiseResult.ADVERTISE_FAILED_FEATURE_UNSUPPORTED -> {
+                        isAdvertising = false
+                        toast("ADVERTISE_FAILED_FEATURE_UNSUPPORTED").show()
+                    }
+
+                    AdvertiseResult.ADVERTISE_FAILED_INTERNAL_ERROR -> {
+                        isAdvertising = false
+                        toast("ADVERTISE_FAILED_INTERNAL_ERROR").show()
+                    }
+
+                    AdvertiseResult.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS -> {
+                        isAdvertising = false
+                        toast("ADVERTISE_FAILED_TOO_MANY_ADVERTISERS").show()
                     }
                 }
+            }
         }
     }
 
@@ -460,19 +461,18 @@
             isGattServerOpen = true
 
             bluetoothLe.openGattServer(viewModel.gattServerServices) {
-                connectRequest.collect {
+                connectRequests.collect {
                     launch {
                         it.accept {
                             requests.collect {
                                 when (it) {
-                                    is GattServerRequest.ReadCharacteristicRequest ->
-                                        it.sendResponse(/*success=*/true,
-                                            ByteBuffer.allocate(Int.SIZE_BYTES).putInt(1)
-                                                .array()
+                                    is GattServerRequest.ReadCharacteristic ->
+                                        it.sendResponse(
+                                            ByteBuffer.allocate(Int.SIZE_BYTES).putInt(1).array()
                                         )
 
-                                    is GattServerRequest.WriteCharacteristicRequest ->
-                                        it.sendResponse(/*success=*/true, null)
+                                    is GattServerRequest.WriteCharacteristic ->
+                                        it.sendResponse(null)
 
                                     else -> throw NotImplementedError("unknown request")
                                 }
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/advertiser/AdvertiserViewModel.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/advertiser/AdvertiserViewModel.kt
index ba55df3..99cb7f8 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/advertiser/AdvertiserViewModel.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/advertiser/AdvertiserViewModel.kt
@@ -32,7 +32,7 @@
     var includeDeviceName = false
     var connectable = false
     var discoverable = false
-    var timeoutMillis = 0
+    var durationMillis = 0
     var manufacturerDatas = mutableListOf<Pair<Int, ByteArray>>()
     var serviceDatas = mutableListOf<Pair<UUID, ByteArray>>()
     var serviceUuids = mutableListOf<UUID>()
@@ -56,7 +56,7 @@
             includeDeviceName,
             connectable,
             discoverable,
-            timeoutMillis,
+            durationMillis,
             manufacturerDatas.toMap(),
             serviceDatas.toMap(),
             serviceUuids
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/ScannerFragment.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/ScannerFragment.kt
index acdb648..186431f 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/ScannerFragment.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/ScannerFragment.kt
@@ -258,10 +258,10 @@
 
             try {
                 bluetoothLe.connectGatt(deviceConnection.bluetoothDevice) {
-                    Log.d(TAG, "connectGatt result: getServices() = ${getServices()}")
+                    Log.d(TAG, "connectGatt result: services() = $services")
 
                     deviceConnection.status = Status.CONNECTED
-                    deviceConnection.services = getServices()
+                    deviceConnection.services = services
                     launch(Dispatchers.Main) {
                         updateDeviceUI(deviceConnection)
                     }
diff --git a/browser/browser/api/current.txt b/browser/browser/api/current.txt
index 13659a7..02dd082 100644
--- a/browser/browser/api/current.txt
+++ b/browser/browser/api/current.txt
@@ -131,9 +131,9 @@
     field public static final String EXTRA_COLOR_SCHEME = "androidx.browser.customtabs.extra.COLOR_SCHEME";
     field public static final String EXTRA_COLOR_SCHEME_PARAMS = "androidx.browser.customtabs.extra.COLOR_SCHEME_PARAMS";
     field @Deprecated public static final String EXTRA_DEFAULT_SHARE_MENU_ITEM = "android.support.customtabs.extra.SHARE_MENU_ITEM";
+    field public static final String EXTRA_DISABLE_BACKGROUND_INTERACTION = "androidx.browser.customtabs.extra.DISABLE_BACKGROUND_INTERACTION";
     field public static final String EXTRA_DISABLE_BOOKMARKS_BUTTON = "org.chromium.chrome.browser.customtabs.EXTRA_DISABLE_STAR_BUTTON";
     field public static final String EXTRA_DISABLE_DOWNLOAD_BUTTON = "org.chromium.chrome.browser.customtabs.EXTRA_DISABLE_DOWNLOAD_BUTTON";
-    field public static final String EXTRA_ENABLE_BACKGROUND_INTERACTION = "androidx.browser.customtabs.extra.ENABLE_BACKGROUND_INTERACTION";
     field public static final String EXTRA_ENABLE_INSTANT_APPS = "android.support.customtabs.extra.EXTRA_ENABLE_INSTANT_APPS";
     field public static final String EXTRA_ENABLE_URLBAR_HIDING = "android.support.customtabs.extra.ENABLE_URLBAR_HIDING";
     field public static final String EXTRA_EXIT_ANIMATION_BUNDLE = "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
diff --git a/browser/browser/api/restricted_current.txt b/browser/browser/api/restricted_current.txt
index ef43681..1e7b718 100644
--- a/browser/browser/api/restricted_current.txt
+++ b/browser/browser/api/restricted_current.txt
@@ -142,9 +142,9 @@
     field public static final String EXTRA_COLOR_SCHEME = "androidx.browser.customtabs.extra.COLOR_SCHEME";
     field public static final String EXTRA_COLOR_SCHEME_PARAMS = "androidx.browser.customtabs.extra.COLOR_SCHEME_PARAMS";
     field @Deprecated public static final String EXTRA_DEFAULT_SHARE_MENU_ITEM = "android.support.customtabs.extra.SHARE_MENU_ITEM";
+    field public static final String EXTRA_DISABLE_BACKGROUND_INTERACTION = "androidx.browser.customtabs.extra.DISABLE_BACKGROUND_INTERACTION";
     field public static final String EXTRA_DISABLE_BOOKMARKS_BUTTON = "org.chromium.chrome.browser.customtabs.EXTRA_DISABLE_STAR_BUTTON";
     field public static final String EXTRA_DISABLE_DOWNLOAD_BUTTON = "org.chromium.chrome.browser.customtabs.EXTRA_DISABLE_DOWNLOAD_BUTTON";
-    field public static final String EXTRA_ENABLE_BACKGROUND_INTERACTION = "androidx.browser.customtabs.extra.ENABLE_BACKGROUND_INTERACTION";
     field public static final String EXTRA_ENABLE_INSTANT_APPS = "android.support.customtabs.extra.EXTRA_ENABLE_INSTANT_APPS";
     field public static final String EXTRA_ENABLE_URLBAR_HIDING = "android.support.customtabs.extra.ENABLE_URLBAR_HIDING";
     field public static final String EXTRA_EXIT_ANIMATION_BUNDLE = "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
diff --git a/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java b/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
index df73c25..877a2f5 100644
--- a/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
+++ b/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
@@ -185,11 +185,11 @@
             "androidx.browser.customtabs.extra.TRANSLATE_LANGUAGE_TAG";
 
     /**
-     * Extra that, when set to false, disables interactions with the background app
-     * when a Partial Custom Tab is launched.
+     * Extra tha disables interactions with the background app when a Partial Custom Tab
+     * is launched.
      */
-    public static final String EXTRA_ENABLE_BACKGROUND_INTERACTION =
-            "androidx.browser.customtabs.extra.ENABLE_BACKGROUND_INTERACTION";
+    public static final String EXTRA_DISABLE_BACKGROUND_INTERACTION =
+            "androidx.browser.customtabs.extra.DISABLE_BACKGROUND_INTERACTION";
 
     /**
      * Extra that enables the client to add an additional action button to the toolbar.
@@ -1173,11 +1173,11 @@
          * Enables the interactions with the background app when a Partial Custom Tab is launched.
          *
          * @param enabled Whether the background interaction is enabled.
-         * @see CustomTabsIntent#EXTRA_ENABLE_BACKGROUND_INTERACTION
+         * @see CustomTabsIntent#EXTRA_DISABLE_BACKGROUND_INTERACTION
          */
         @NonNull
         public Builder setBackgroundInteractionEnabled(boolean enabled) {
-            mIntent.putExtra(EXTRA_ENABLE_BACKGROUND_INTERACTION, enabled);
+            mIntent.putExtra(EXTRA_DISABLE_BACKGROUND_INTERACTION, !enabled);
             return this;
         }
 
@@ -1456,10 +1456,10 @@
 
     /**
      * @return Whether the background interaction is enabled.
-     * @see CustomTabsIntent#EXTRA_ENABLE_BACKGROUND_INTERACTION
+     * @see CustomTabsIntent#EXTRA_DISABLE_BACKGROUND_INTERACTION
      */
     public static boolean isBackgroundInteractionEnabled(@NonNull Intent intent) {
-        return intent.getBooleanExtra(EXTRA_ENABLE_BACKGROUND_INTERACTION, false);
+        return !intent.getBooleanExtra(EXTRA_DISABLE_BACKGROUND_INTERACTION, false);
     }
 
     /**
diff --git a/browser/browser/src/test/java/androidx/browser/customtabs/CustomTabsIntentTest.java b/browser/browser/src/test/java/androidx/browser/customtabs/CustomTabsIntentTest.java
index 08dea64..aee0f19 100644
--- a/browser/browser/src/test/java/androidx/browser/customtabs/CustomTabsIntentTest.java
+++ b/browser/browser/src/test/java/androidx/browser/customtabs/CustomTabsIntentTest.java
@@ -586,16 +586,17 @@
     @Test
     public void testBackgroundInteraction() {
         Intent intent = new CustomTabsIntent.Builder().build().intent;
-        assertFalse(CustomTabsIntent.isBackgroundInteractionEnabled(intent));
+        assertTrue(CustomTabsIntent.isBackgroundInteractionEnabled(intent));
 
         intent = new CustomTabsIntent.Builder()
-                .setBackgroundInteractionEnabled(false).build().intent;
-        assertFalse(CustomTabsIntent.isBackgroundInteractionEnabled(intent));
-
-        // The extra is set to true only when explicitly called to enable it.
-        intent = new CustomTabsIntent.Builder()
                 .setBackgroundInteractionEnabled(true).build().intent;
         assertTrue(CustomTabsIntent.isBackgroundInteractionEnabled(intent));
+
+        // The extra (EXTRA_DISABLE_BACKGROUND_INTERACTION) is set to true
+        // only when explicitly called to disable it.
+        intent = new CustomTabsIntent.Builder()
+                .setBackgroundInteractionEnabled(false).build().intent;
+        assertFalse(CustomTabsIntent.isBackgroundInteractionEnabled(intent));
     }
 
     @Test
diff --git a/buildSrc-tests/src/test/java/androidx/build/SourceJarTaskHelperTest.kt b/buildSrc-tests/src/test/java/androidx/build/SourceJarTaskHelperTest.kt
new file mode 100644
index 0000000..3e82e00
--- /dev/null
+++ b/buildSrc-tests/src/test/java/androidx/build/SourceJarTaskHelperTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build
+
+import com.google.common.truth.Truth.assertThat
+import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
+import org.junit.Test
+
+class SourceJarTaskHelperTest {
+    @Test
+    fun generateMetadata() {
+        val project = ProjectBuilder.builder().build()
+        project.plugins.apply(KotlinMultiplatformPluginWrapper::class.java)
+        val extension = project.multiplatformExtension!!
+        extension.jvm()
+        val commonMain = extension.sourceSets.getByName("commonMain")
+        val jvmMain = extension.sourceSets.getByName("jvmMain")
+        val extraMain = extension.sourceSets.create("extraMain")
+        extraMain.dependsOn(commonMain)
+        jvmMain.dependsOn(commonMain)
+        jvmMain.dependsOn(extraMain)
+
+        val result = createSourceSetMetadata(extension)
+        assertThat(result).isEqualTo("""
+        {
+          "sourceSets": [
+            {
+              "name": "commonMain",
+              "dependencies": [],
+              "analysisPlatform": "common"
+            },
+            {
+              "name": "extraMain",
+              "dependencies": [
+                "commonMain"
+              ],
+              "analysisPlatform": "jvm"
+            },
+            {
+              "name": "jvmMain",
+              "dependencies": [
+                "commonMain",
+                "extraMain"
+              ],
+              "analysisPlatform": "jvm"
+            }
+          ]
+        }
+        """.trimIndent())
+    }
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 73b8580..5fe690c 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -460,6 +460,9 @@
                 it.artRewritingWorkaround()
             }
         }
+
+        project.buildOnServerDependsOnAssembleRelease()
+        project.buildOnServerDependsOnLint()
     }
 
     private fun configureWithTestPlugin(project: Project, androidXExtension: AndroidXExtension) {
@@ -473,6 +476,30 @@
         project.addToProjectMap(androidXExtension)
     }
 
+    private fun Project.buildOnServerDependsOnAssembleRelease() {
+        project.addToBuildOnServer("assembleRelease")
+    }
+
+    private fun Project.buildOnServerDependsOnLint() {
+        if (!project.usingMaxDepVersions()) {
+            project.agpVariants.all { variant ->
+                // in AndroidX, release and debug variants are essentially the same,
+                // so we don't run the lintRelease task on the build server
+                if (!variant.name.lowercase(Locale.getDefault()).contains("release")) {
+                    val taskName =
+                        "lint${variant.name.replaceFirstChar {
+                        if (it.isLowerCase()) {
+                            it.titlecase(Locale.getDefault())
+                        } else {
+                            it.toString()
+                        }
+                    }}"
+                    project.addToBuildOnServer(taskName)
+                }
+            }
+        }
+    }
+
     private fun HasAndroidTest.configureTests() {
         configureLicensePackaging()
         excludeVersionFilesFromTestApks()
@@ -595,6 +622,9 @@
         )
 
         project.addToProjectMap(androidXExtension)
+
+        project.buildOnServerDependsOnAssembleRelease()
+        project.buildOnServerDependsOnLint()
     }
 
     private fun configureWithJavaPlugin(project: Project, extension: AndroidXExtension) {
@@ -657,6 +687,8 @@
             configuration.resolutionStrategy.preferProjectModules()
         }
 
+        project.addToBuildOnServer("jar")
+
         project.addToProjectMap(extension)
     }
 
@@ -792,14 +824,20 @@
             File(project.buildDir, "../nativeBuildStaging")
     }
 
+    @Suppress("UnstableApiUsage") // finalizeDsl, minCompileSdkExtension
     private fun LibraryExtension.configureAndroidLibraryOptions(
         project: Project,
         androidXExtension: AndroidXExtension
     ) {
-        // Note, this should really match COMPILE_SDK_VERSION, however
-        // this API takes an integer and we are unable to set it to a
-        // pre-release SDK.
-        defaultConfig.aarMetadata.minCompileSdk = project.defaultAndroidConfig.targetSdk
+        // Propagate the compileSdk value into minCompileSdk. Don't propagate compileSdkExtension,
+        // since only one library actually depends on the extension APIs and they can explicitly
+        // declare that in their build.gradle. Note that when we're using a preview SDK, the value
+        // for compileSdk will be null and the resulting AAR metadata won't have a minCompileSdk --
+        // this is okay because AGP automatically embeds forceCompileSdkPreview in the AAR metadata
+        // and uses it instead of minCompileSdk.
+        project.extensions.findByType<LibraryAndroidComponentsExtension>()!!.finalizeDsl {
+            it.defaultConfig.aarMetadata.minCompileSdk = it.compileSdk
+        }
 
         // The full Guava artifact is very large, so they split off a special artifact containing a
         // standalone version of the commonly-used ListenableFuture interface. However, they also
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
index fc33853..9eb83d2 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXRootImplPlugin.kt
@@ -29,16 +29,12 @@
 import androidx.build.uptodatedness.TaskUpToDateValidator
 import androidx.build.uptodatedness.cacheEvenIfNoOutputs
 import com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
-import com.android.build.gradle.AppPlugin
-import com.android.build.gradle.LibraryPlugin
 import java.io.File
-import java.util.Locale
 import java.util.concurrent.ConcurrentHashMap
 import org.gradle.api.GradleException
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.artifacts.component.ModuleComponentSelector
-import org.gradle.api.plugins.JavaPlugin
 import org.gradle.api.plugins.JvmEcosystemPlugin
 import org.gradle.api.tasks.bundling.Zip
 import org.gradle.api.tasks.bundling.ZipEntryCompression
@@ -101,37 +97,6 @@
         }
 
         extra.set("projects", ConcurrentHashMap<String, String>())
-        subprojects { project ->
-            project.afterEvaluate {
-                if (
-                    project.plugins.hasPlugin(LibraryPlugin::class.java) ||
-                        project.plugins.hasPlugin(AppPlugin::class.java)
-                ) {
-
-                    buildOnServerTask.dependsOn("${project.path}:assembleRelease")
-                    if (!project.usingMaxDepVersions()) {
-                        project.agpVariants.all { variant ->
-                            // in AndroidX, release and debug variants are essentially the same,
-                            // so we don't run the lintRelease task on the build server
-                            if (!variant.name.lowercase(Locale.getDefault()).contains("release")) {
-                                val taskName =
-                                    "lint${variant.name.replaceFirstChar {
-                                    if (it.isLowerCase()) {
-                                        it.titlecase(Locale.getDefault())
-                                    } else {
-                                        it.toString()
-                                    }
-                                }}"
-                                buildOnServerTask.dependsOn("${project.path}:$taskName")
-                            }
-                        }
-                    }
-                }
-            }
-            project.plugins.withType(JavaPlugin::class.java) {
-                buildOnServerTask.dependsOn("${project.path}:jar")
-            }
-        }
 
         // NOTE: this task is used by the Github CI as well. If you make any changes here,
         // please update the .github/workflows files as well, if necessary.
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
index 5a881a2..a307c9d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -345,6 +345,9 @@
             disable.add("LintError")
         }
 
+        // Disable a check that's only relevant for apps that ship to Play Store. (b/299278101)
+        disable.add("ExpiredTargetSdkVersion")
+
         // Reenable after b/238892319 is resolved
         disable.add("NotificationPermission")
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt b/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
index cd60cc3..9380bb9 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
@@ -443,6 +443,9 @@
         scm.url.set("https://cs.android.com/androidx/platform/frameworks/support")
         scm.connection.set(ANDROID_GIT_URL)
     }
+    pom.organization { org ->
+        org.name.set("The Android Open Source Project")
+    }
     pom.developers { devs ->
         devs.developer { dev -> dev.name.set("The Android Open Source Project") }
     }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt b/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
index 1e6ed89..04c4da3 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
@@ -258,7 +258,7 @@
             "commonMain" to
                 mapOf(
                     "name" to commonMain.name,
-                    "dependencies" to commonMain.dependsOn.map { it.name },
+                    "dependencies" to commonMain.dependsOn.map { it.name }.sorted(),
                     "analysisPlatform" to DokkaAnalysisPlatform.COMMON.jsonName
                 )
         )
@@ -267,13 +267,15 @@
             sourceSetsByName.getOrPut(it.name) {
                 mapOf(
                     "name" to it.name,
-                    "dependencies" to it.dependsOn.map { it.name },
+                    "dependencies" to it.dependsOn.map { it.name }.sorted(),
                     "analysisPlatform" to target.docsPlatform().jsonName
                 )
             }
         }
     }
-    val sourceSetMetadata = mutableMapOf("sourceSets" to sourceSetsByName.values)
+    val sourceSetMetadata = mapOf(
+        "sourceSets" to sourceSetsByName.keys.sorted().map { sourceSetsByName[it] }
+    )
     val gson = GsonBuilder().setPrettyPrinting().create()
     return gson.toJson(sourceSetMetadata)
 }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
index c7006f8..144ed14 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
@@ -34,6 +34,7 @@
 import org.gradle.api.tasks.Input
 import org.gradle.api.tasks.InputFile
 import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Internal
 import org.gradle.api.tasks.OutputDirectory
 import org.gradle.api.tasks.PathSensitive
 import org.gradle.api.tasks.PathSensitivity
@@ -49,6 +50,8 @@
 constructor(private val workerExecutor: WorkerExecutor, private val objects: ObjectFactory) :
     DefaultTask() {
 
+    @Internal lateinit var argsJsonFile: File
+
     @get:[InputFiles PathSensitive(PathSensitivity.RELATIVE)]
     abstract val projectStructureMetadataFile: RegularFileProperty
 
@@ -220,10 +223,8 @@
             )
 
         val json = gson.toJson(jsonMap)
-        val outputFile = File.createTempFile("dackkaArgs", ".json")
-        outputFile.deleteOnExit()
-        outputFile.writeText(json)
-        return outputFile
+        argsJsonFile.writeText(json)
+        return argsJsonFile
     }
 
     /**
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
index e39904c2..e6ccb06 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
@@ -487,6 +487,10 @@
         val dackkaTask =
             project.tasks.register("docs", DackkaTask::class.java) { task ->
                 var taskStartTime: LocalDateTime? = null
+                task.argsJsonFile = File(
+                    project.rootProject.getDistributionDirectory(),
+                    "dackkaArgs-${project.name}.json"
+                )
                 task.apply {
                     dependsOn(unzipJvmSourcesTask)
                     dependsOn(unzipSamplesTask)
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/sbom/Sbom.kt b/buildSrc/private/src/main/kotlin/androidx/build/sbom/Sbom.kt
index 669aa02..7b470d9 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/sbom/Sbom.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/sbom/Sbom.kt
@@ -239,7 +239,7 @@
 
         task.taskExtension.set(
             object : DefaultSpdxSbomTaskExtension() {
-                override fun mapRepoUri(repoUri: URI, artifact: ModuleVersionIdentifier): URI {
+                override fun mapRepoUri(repoUri: URI?, artifact: ModuleVersionIdentifier): URI {
                     val uriString = repoUri.toString()
                     for (repo in repos) {
                         val ourRepoUrl = repo.key
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/BuildOnServer.kt b/buildSrc/public/src/main/kotlin/androidx/build/BuildOnServer.kt
index c29f465..99ed273 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/BuildOnServer.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/BuildOnServer.kt
@@ -22,12 +22,12 @@
 
 const val BUILD_ON_SERVER_TASK = "buildOnServer"
 
-/** Configures the root project's buildOnServer task to run the specified task. */
+/** Configures the project's buildOnServer task to run the specified task. */
 fun <T : Task> Project.addToBuildOnServer(taskProvider: TaskProvider<T>) {
     tasks.named(BUILD_ON_SERVER_TASK).configure { it.dependsOn(taskProvider) }
 }
 
-/** Configures the root project's buildOnServer task to run the specified task. */
-fun <T : Task> Project.addToBuildOnServer(taskPath: String) {
+/** Configures the project's buildOnServer task to run the specified task. */
+fun Project.addToBuildOnServer(taskPath: String) {
     tasks.named(BUILD_ON_SERVER_TASK).configure { it.dependsOn(taskPath) }
 }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt
index f8d6362..6fd951c 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt
@@ -210,6 +210,14 @@
         return setOf(SDR)
     }
 
+    override fun isPreviewStabilizationSupported(): Boolean {
+        return false
+    }
+
+    override fun isVideoStabilizationSupported(): Boolean {
+        return false
+    }
+
     private fun profileSetToDynamicRangeSet(profileSet: Set<Long>): Set<DynamicRange> {
         return profileSet.map { profileToDynamicRange(it) }.toSet()
     }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraSurfaceAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraSurfaceAdapter.kt
index e8b59257..72d3930 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraSurfaceAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraSurfaceAdapter.kt
@@ -122,6 +122,7 @@
      * @param newUseCaseConfigsSupportedSizeMap map of configurations of the use cases to the
      *                                          supported sizes list that will be given a
      *                                          suggested stream specification
+     * @param isPreviewStabilizationOn          whether the preview stabilization is enabled.
      * @return map of suggested stream specifications for given use cases
      * @throws IllegalArgumentException if {@code newUseCaseConfigs} is an empty list, if
      *                                  there isn't a supported combination of surfaces
@@ -132,7 +133,8 @@
         cameraMode: Int,
         cameraId: String,
         existingSurfaces: List<AttachedSurfaceInfo>,
-        newUseCaseConfigsSupportedSizeMap: Map<UseCaseConfig<*>, List<Size>>
+        newUseCaseConfigsSupportedSizeMap: Map<UseCaseConfig<*>, List<Size>>,
+        isPreviewStabilizationOn: Boolean
     ): Pair<Map<UseCaseConfig<*>, StreamSpec>, Map<AttachedSurfaceInfo, StreamSpec>> {
 
         if (!checkIfSupportedCombinationExist(cameraId)) {
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraUseCaseAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraUseCaseAdapter.kt
index 8361fb3..7d1c04f 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraUseCaseAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraUseCaseAdapter.kt
@@ -81,13 +81,17 @@
         when (captureType) {
             CaptureType.IMAGE_CAPTURE,
             CaptureType.PREVIEW,
+                // Uses TEMPLATE_PREVIEW instead of TEMPLATE_RECORD for StreamSharing. Since there
+                // is a issue that captured results being stretched when requested for recording on
+                // some models, it would be safer to request for preview, which is also better
+                // tested. More detail please see b/297167569.
+            CaptureType.STREAM_SHARING,
             CaptureType.METERING_REPEATING,
             CaptureType.IMAGE_ANALYSIS -> sessionBuilder.setTemplateType(
                 CameraDevice.TEMPLATE_PREVIEW
             )
 
-            CaptureType.VIDEO_CAPTURE,
-            CaptureType.STREAM_SHARING -> sessionBuilder.setTemplateType(
+            CaptureType.VIDEO_CAPTURE -> sessionBuilder.setTemplateType(
                 CameraDevice.TEMPLATE_RECORD
             )
         }
@@ -102,9 +106,13 @@
 
             CaptureType.PREVIEW,
             CaptureType.IMAGE_ANALYSIS,
-            CaptureType.VIDEO_CAPTURE,
+                // Uses TEMPLATE_PREVIEW instead of TEMPLATE_RECORD for StreamSharing to align with
+                // SessionConfig's setup. More detail please see b/297167569.
             CaptureType.STREAM_SHARING,
             CaptureType.METERING_REPEATING ->
+                captureBuilder.templateType = CameraDevice.TEMPLATE_PREVIEW
+
+            CaptureType.VIDEO_CAPTURE ->
                 captureBuilder.templateType = CameraDevice.TEMPLATE_RECORD
         }
         mutableConfig.insertOption(
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/GuaranteedConfigurationsUtil.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/GuaranteedConfigurationsUtil.kt
index ce74b04..33fac2e 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/GuaranteedConfigurationsUtil.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/GuaranteedConfigurationsUtil.kt
@@ -718,6 +718,56 @@
         return surfaceCombinations
     }
 
+    /**
+     * Returns the minimally guaranteed stream combinations when one or more
+     * streams are configured as a 10-bit input.
+     */
+    @JvmStatic
+    fun get10BitSupportedCombinationList(): List<SurfaceCombination> {
+        return listOf(
+            // (PRIV, MAXIMUM)
+            SurfaceCombination().apply {
+                addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.MAXIMUM))
+            },
+            // (YUV, MAXIMUM)
+            SurfaceCombination().apply {
+                addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM))
+            },
+            // (PRIV, PREVIEW) + (JPEG, MAXIMUM)
+            SurfaceCombination().apply {
+                addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+                addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM))
+            },
+            // (PRIV, PREVIEW) + (YUV, MAXIMUM)
+            SurfaceCombination().apply {
+                addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+                addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM))
+            },
+            // (YUV, PREVIEW) + (YUV, MAXIMUM)
+            SurfaceCombination().apply {
+                addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW))
+                addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM))
+            },
+            // (PRIV, PREVIEW) + (PRIV, RECORD)
+            SurfaceCombination().apply {
+                addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+                addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD))
+            },
+            // (PRIV, PREVIEW) + (PRIV, RECORD) + (YUV, RECORD)
+            SurfaceCombination().apply {
+                addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+                addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD))
+                addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD))
+            },
+            // (PRIV, PREVIEW) + (PRIV, RECORD) + (JPEG, RECORD)
+            SurfaceCombination().apply {
+                addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+                addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD))
+                addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.RECORD))
+            },
+        )
+    }
+
     @JvmStatic
     fun generateConcurrentSupportedCombinationList(): List<SurfaceCombination> {
         val surfaceCombinations: MutableList<SurfaceCombination> = arrayListOf()
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt
index daa54a8..2122ec2 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt
@@ -18,20 +18,19 @@
 
 import android.annotation.SuppressLint
 import android.content.Context
+import android.content.pm.PackageManager
 import android.content.pm.PackageManager.FEATURE_CAMERA_CONCURRENT
 import android.graphics.ImageFormat
-import android.graphics.Point
 import android.graphics.SurfaceTexture
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.params.StreamConfigurationMap
-import android.hardware.display.DisplayManager
 import android.media.CamcorderProfile
 import android.media.MediaRecorder
 import android.os.Build
 import android.util.Pair
+import android.util.Range
 import android.util.Rational
 import android.util.Size
-import android.view.Display
 import androidx.annotation.RequiresApi
 import androidx.annotation.VisibleForTesting
 import androidx.camera.camera2.pipe.CameraMetadata
@@ -41,6 +40,8 @@
 import androidx.camera.camera2.pipe.integration.compat.workaround.ResolutionCorrector
 import androidx.camera.camera2.pipe.integration.compat.workaround.TargetAspectRatio
 import androidx.camera.camera2.pipe.integration.impl.DisplayInfoManager
+import androidx.camera.camera2.pipe.integration.internal.DynamicRangeResolver
+import androidx.camera.core.DynamicRange
 import androidx.camera.core.impl.AttachedSurfaceInfo
 import androidx.camera.core.impl.CameraMode
 import androidx.camera.core.impl.EncoderProfilesProxy
@@ -60,6 +61,8 @@
 import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_VGA
 import java.util.Arrays
 import java.util.Collections
+import kotlin.math.floor
+import kotlin.math.min
 
 /**
  * Camera device supported surface configuration combinations
@@ -85,22 +88,22 @@
     private val concurrentSurfaceCombinations: MutableList<SurfaceCombination> = mutableListOf()
     private val surfaceCombinations: MutableList<SurfaceCombination> = mutableListOf()
     private val ultraHighSurfaceCombinations: MutableList<SurfaceCombination> = mutableListOf()
-    private val cameraModeToSupportedCombinationsMap: MutableMap<Int, List<SurfaceCombination>> =
-        mutableMapOf()
+    private val featureSettingsToSupportedCombinationsMap:
+        MutableMap<FeatureSettings, List<SurfaceCombination>> = mutableMapOf()
+    private val surfaceCombinations10Bit: MutableList<SurfaceCombination> = mutableListOf()
     private var isRawSupported = false
     private var isBurstCaptureSupported = false
     private var isConcurrentCameraModeSupported = false
     private var isUltraHighResolutionSensorSupported = false
     internal lateinit var surfaceSizeDefinition: SurfaceSizeDefinition
     private val surfaceSizeDefinitionFormats = mutableListOf<Int>()
-    private val displayManager: DisplayManager =
-        (context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager)
     private val streamConfigurationMapCompat = getStreamConfigurationMapCompat()
     private val extraSupportedSurfaceCombinationsContainer =
         ExtraSupportedSurfaceCombinationsContainer()
     private val displayInfoManager = DisplayInfoManager(context)
     private val resolutionCorrector = ResolutionCorrector()
     private val targetAspectRatio: TargetAspectRatio = TargetAspectRatio()
+    private val dynamicRangeResolver: DynamicRangeResolver = DynamicRangeResolver(cameraMetadata)
 
     init {
         checkCapabilities()
@@ -113,6 +116,10 @@
         if (isConcurrentCameraModeSupported) {
             generateConcurrentSupportedCombinationList()
         }
+
+        if (dynamicRangeResolver.is10BitDynamicRangeSupported()) {
+            generate10BitSupportedCombinationList()
+        }
         generateSurfaceSizeDefinition()
     }
 
@@ -120,48 +127,50 @@
      * Check whether the input surface configuration list is under the capability of any combination
      * of this object.
      *
-     * @param cameraMode        the working camera mode.
+     * @param featureSettings  the settings for the camera's features/capabilities.
      * @param surfaceConfigList the surface configuration list to be compared
+     *
      * @return the check result that whether it could be supported
      */
     fun checkSupported(
-        cameraMode: Int,
+        featureSettings: FeatureSettings,
         surfaceConfigList: List<SurfaceConfig>
     ): Boolean {
-        // TODO(b/262772650): camera-pipe support for concurrent camera
-        val targetSurfaceCombinations = getSurfaceCombinationsByCameraMode(cameraMode)
-        for (surfaceCombination in targetSurfaceCombinations) {
-            if (surfaceCombination
-                    .getOrderedSupportedSurfaceConfigList(surfaceConfigList) != null
-            ) {
-                return true
-            }
+        return getSurfaceCombinationsByFeatureSettings(featureSettings).any {
+            it.getOrderedSupportedSurfaceConfigList(surfaceConfigList) != null
         }
-        return false
     }
 
     /**
-     * Returns the supported surface combinations according to the specified camera mode.
+     * Returns the supported surface combinations according to the specified feature
+     * settings.
      */
-    private fun getSurfaceCombinationsByCameraMode(
-        @CameraMode.Mode cameraMode: Int
+    private fun getSurfaceCombinationsByFeatureSettings(
+        featureSettings: FeatureSettings
     ): List<SurfaceCombination> {
-        if (cameraModeToSupportedCombinationsMap.containsKey(cameraMode)) {
-            return cameraModeToSupportedCombinationsMap[cameraMode]!!
+        if (featureSettingsToSupportedCombinationsMap.containsKey(featureSettings)) {
+            return featureSettingsToSupportedCombinationsMap[featureSettings]!!
         }
         var supportedSurfaceCombinations: MutableList<SurfaceCombination> = mutableListOf()
-        when (cameraMode) {
-            CameraMode.CONCURRENT_CAMERA -> supportedSurfaceCombinations =
-                concurrentSurfaceCombinations
+        if (featureSettings.requiredMaxBitDepth == DynamicRange.BIT_DEPTH_8_BIT) {
+            when (featureSettings.cameraMode) {
+                CameraMode.CONCURRENT_CAMERA -> supportedSurfaceCombinations =
+                    concurrentSurfaceCombinations
 
-            CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA -> {
-                supportedSurfaceCombinations.addAll(ultraHighSurfaceCombinations)
-                supportedSurfaceCombinations.addAll(surfaceCombinations)
+                CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA -> {
+                    supportedSurfaceCombinations.addAll(ultraHighSurfaceCombinations)
+                    supportedSurfaceCombinations.addAll(surfaceCombinations)
+                }
+
+                else -> supportedSurfaceCombinations.addAll(surfaceCombinations)
             }
-
-            else -> supportedSurfaceCombinations.addAll(surfaceCombinations)
+        } else if (featureSettings.requiredMaxBitDepth == DynamicRange.BIT_DEPTH_10_BIT) {
+            // For 10-bit outputs, only the default camera mode is currently supported.
+            if (featureSettings.cameraMode == CameraMode.DEFAULT) {
+                supportedSurfaceCombinations.addAll(surfaceCombinations10Bit)
+            }
         }
-        cameraModeToSupportedCombinationsMap[cameraMode] = supportedSurfaceCombinations
+        featureSettingsToSupportedCombinationsMap[featureSettings] = supportedSurfaceCombinations
         return supportedSurfaceCombinations
     }
 
@@ -188,7 +197,7 @@
      * Finds the suggested stream specification of the newly added UseCaseConfig.
      *
      * @param cameraMode        the working camera mode.
-     * @param existingSurfaces  the existing surfaces.
+     * @param attachedSurfaces  the existing surfaces.
      * @param newUseCaseConfigsSupportedSizeMap newly added UseCaseConfig to supported output sizes
      * map.
      * @return the suggested stream specs, which is a mapping from UseCaseConfig to the suggested
@@ -198,15 +207,34 @@
      */
     fun getSuggestedStreamSpecifications(
         cameraMode: Int,
-        existingSurfaces: List<AttachedSurfaceInfo>,
+        attachedSurfaces: List<AttachedSurfaceInfo>,
         newUseCaseConfigsSupportedSizeMap: Map<UseCaseConfig<*>, List<Size>>
     ): Pair<Map<UseCaseConfig<*>, StreamSpec>, Map<AttachedSurfaceInfo, StreamSpec>> {
+        // Refresh Preview Size based on current display configurations.
         refreshPreviewSize()
-        val surfaceConfigs: MutableList<SurfaceConfig> = ArrayList()
-        for (scc in existingSurfaces) {
+        val surfaceConfigs: MutableList<SurfaceConfig> = mutableListOf()
+        for (scc in attachedSurfaces) {
             surfaceConfigs.add(scc.surfaceConfig)
         }
         val newUseCaseConfigs = newUseCaseConfigsSupportedSizeMap.keys.toList()
+
+        // Get the index order list by the use case priority for finding stream configuration
+        val useCasesPriorityOrder = getUseCasesPriorityOrder(newUseCaseConfigs)
+        val resolvedDynamicRanges = dynamicRangeResolver.resolveAndValidateDynamicRanges(
+            attachedSurfaces,
+            newUseCaseConfigs, useCasesPriorityOrder
+        )
+        val requiredMaxBitDepth: Int = getRequiredMaxBitDepth(resolvedDynamicRanges)
+        val featureSettings = FeatureSettings(cameraMode, requiredMaxBitDepth)
+        require(
+            !(cameraMode != CameraMode.DEFAULT &&
+                requiredMaxBitDepth == DynamicRange.BIT_DEPTH_10_BIT)
+        ) {
+            "No supported surface combination is " +
+                "found for camera device - Id : $cameraId. 10 bit dynamic range is not " +
+                "currently supported in ${CameraMode.toLabelString(cameraMode)} camera mode."
+        }
+
         // Use the small size (640x480) for new use cases to check whether there is any possible
         // supported combination first
         for (useCaseConfig in newUseCaseConfigs) {
@@ -220,83 +248,476 @@
             )
         }
 
-        if (!checkSupported(cameraMode, surfaceConfigs)) {
+        if (!checkSupported(featureSettings, surfaceConfigs)) {
             throw java.lang.IllegalArgumentException(
                 "No supported surface combination is found for camera device - Id : " + cameraId +
                     ".  May be attempting to bind too many use cases. " + "Existing surfaces: " +
-                    existingSurfaces + " New configs: " + newUseCaseConfigs
+                    attachedSurfaces + " New configs: " + newUseCaseConfigs
             )
         }
-        // Get the index order list by the use case priority for finding stream configuration
-        val useCasesPriorityOrder: List<Int> = getUseCasesPriorityOrder(
-            newUseCaseConfigs
+
+        val targetFpsRange =
+            getTargetFpsRange(attachedSurfaces, newUseCaseConfigs, useCasesPriorityOrder)
+        val maxSupportedFps = getMaxSupportedFps(attachedSurfaces)
+
+        val bestSizesAndFps = findBestSizesAndFps(
+            newUseCaseConfigsSupportedSizeMap,
+            attachedSurfaces,
+            newUseCaseConfigs,
+            maxSupportedFps,
+            useCasesPriorityOrder,
+            targetFpsRange,
+            featureSettings
         )
-        val supportedOutputSizesList: MutableList<List<Size>> = ArrayList()
+
+        val suggestedStreamSpecMap = generateSuggestedStreamSpecMap(
+            bestSizesAndFps.first,
+            targetFpsRange,
+            bestSizesAndFps.second,
+            newUseCaseConfigs,
+            useCasesPriorityOrder,
+            resolvedDynamicRanges,
+        )
+
+        return Pair.create(suggestedStreamSpecMap, mapOf<AttachedSurfaceInfo, StreamSpec>())
+    }
+
+    private fun getSupportedOutputSizesList(
+        newUseCaseConfigsSupportedSizeMap: Map<UseCaseConfig<*>, List<Size>>,
+        newUseCaseConfigs: List<UseCaseConfig<*>>,
+        useCasesPriorityOrder: List<Int>,
+    ): List<List<Size>> {
+        val supportedOutputSizesList: MutableList<List<Size>> = mutableListOf()
 
         // Collect supported output sizes for all use cases
         for (index in useCasesPriorityOrder) {
-            var supportedOutputSizes: List<Size> =
-                newUseCaseConfigsSupportedSizeMap[newUseCaseConfigs[index]]!!
+            var supportedOutputSizes = newUseCaseConfigsSupportedSizeMap[newUseCaseConfigs[index]]!!
             supportedOutputSizes = applyResolutionSelectionOrderRelatedWorkarounds(
                 supportedOutputSizes,
                 newUseCaseConfigs[index].inputFormat
             )
             supportedOutputSizesList.add(supportedOutputSizes)
         }
-        // Get all possible size arrangements
-        val allPossibleSizeArrangements: List<List<Size>> = getAllPossibleSizeArrangements(
-            supportedOutputSizesList
-        )
+        return supportedOutputSizesList
+    }
 
-        var suggestedStreamSpecMap: Map<UseCaseConfig<*>, StreamSpec>? = null
+    private fun getTargetFpsRange(
+        attachedSurfaces: List<AttachedSurfaceInfo>,
+        newUseCaseConfigs: List<UseCaseConfig<*>>,
+        useCasesPriorityOrder: List<Int>
+    ): Range<Int>? {
+        var targetFrameRateForConfig: Range<Int>? = null
+        for (attachedSurfaceInfo in attachedSurfaces) {
+            // init target fps range for new configs from existing surfaces
+            targetFrameRateForConfig = getUpdatedTargetFrameRate(
+                attachedSurfaceInfo.targetFrameRate,
+                targetFrameRateForConfig
+            )
+        }
+        // update target fps for new configs using new use cases' priority order
+        for (index in useCasesPriorityOrder) {
+            targetFrameRateForConfig = getUpdatedTargetFrameRate(
+                newUseCaseConfigs[index].getTargetFrameRate(null),
+                targetFrameRateForConfig
+            )
+        }
+        return targetFrameRateForConfig
+    }
+
+    private fun getMaxSupportedFps(
+        attachedSurfaces: List<AttachedSurfaceInfo>,
+    ): Int {
+        var existingSurfaceFrameRateCeiling = Int.MAX_VALUE
+        for (attachedSurfaceInfo in attachedSurfaces) {
+            // get the fps ceiling for existing surfaces
+            existingSurfaceFrameRateCeiling = getUpdatedMaximumFps(
+                existingSurfaceFrameRateCeiling,
+                attachedSurfaceInfo.imageFormat, attachedSurfaceInfo.size
+            )
+        }
+        return existingSurfaceFrameRateCeiling
+    }
+
+    private fun findBestSizesAndFps(
+        newUseCaseConfigsSupportedSizeMap: Map<UseCaseConfig<*>, List<Size>>,
+        attachedSurfaces: List<AttachedSurfaceInfo>,
+        newUseCaseConfigs: List<UseCaseConfig<*>>,
+        existingSurfaceFrameRateCeiling: Int,
+        useCasesPriorityOrder: List<Int>,
+        targetFrameRateForConfig: Range<Int>?,
+        featureSettings: FeatureSettings
+    ): Pair<List<Size>, Int> {
+        var bestSizes: List<Size>? = null
+        var bestConfigMaxFps = Int.MAX_VALUE
+        val allPossibleSizeArrangements = getAllPossibleSizeArrangements(
+            getSupportedOutputSizesList(
+                newUseCaseConfigsSupportedSizeMap,
+                newUseCaseConfigs,
+                useCasesPriorityOrder
+            )
+        )
         // Transform use cases to SurfaceConfig list and find the first (best) workable combination
         for (possibleSizeList in allPossibleSizeArrangements) {
             // Attach SurfaceConfig of original use cases since it will impact the new use cases
-            val surfaceConfigList: MutableList<SurfaceConfig> = ArrayList()
-            for (sc in existingSurfaces) {
-                surfaceConfigList.add(sc.surfaceConfig)
-            }
-
-            // Attach SurfaceConfig of new use cases
-            for (i in possibleSizeList.indices) {
-                val size = possibleSizeList[i]
-                val newUseCase = newUseCaseConfigs[useCasesPriorityOrder[i]]
-                surfaceConfigList.add(
-                    SurfaceConfig.transformSurfaceConfig(
-                        cameraMode,
-                        newUseCase.inputFormat,
-                        size,
-                        getUpdatedSurfaceSizeDefinitionByFormat(newUseCase.inputFormat)
-                    )
-                )
-            }
-
-            // Check whether the SurfaceConfig combination can be supported
-            if (checkSupported(cameraMode, surfaceConfigList)) {
-                suggestedStreamSpecMap = HashMap()
-                for (useCaseConfig in newUseCaseConfigs) {
-                    suggestedStreamSpecMap.put(
-                        useCaseConfig,
-                        StreamSpec.builder(
-                            possibleSizeList[useCasesPriorityOrder.indexOf(
-                                newUseCaseConfigs.indexOf(useCaseConfig)
-                            )]
-                        ).build()
-                    )
+            val surfaceConfigList = getSurfaceConfigList(
+                featureSettings.cameraMode,
+                attachedSurfaces, possibleSizeList, newUseCaseConfigs,
+                useCasesPriorityOrder
+            )
+            val currentConfigFrameRateCeiling = getCurrentConfigFrameRateCeiling(
+                possibleSizeList, newUseCaseConfigs,
+                useCasesPriorityOrder, existingSurfaceFrameRateCeiling
+            )
+            var isConfigFrameRateAcceptable = true
+            if (targetFrameRateForConfig != null) {
+                if (existingSurfaceFrameRateCeiling > currentConfigFrameRateCeiling &&
+                    currentConfigFrameRateCeiling < targetFrameRateForConfig.lower
+                ) {
+                    // if the max fps before adding new use cases supports our target fps range
+                    // BUT the max fps of the new configuration is below
+                    // our target fps range, we'll want to check the next configuration until we
+                    // get one that supports our target FPS
+                    isConfigFrameRateAcceptable = false
                 }
-                break
+            }
+
+            // only change the saved config if you get another that has a better max fps
+            if (checkSupported(featureSettings, surfaceConfigList)) {
+                // if we have a configuration where the max fps is acceptable for our target, break
+                if (isConfigFrameRateAcceptable) {
+                    bestConfigMaxFps = currentConfigFrameRateCeiling
+                    bestSizes = possibleSizeList
+                    break
+                }
+                // if the config is supported by the device but doesn't meet the target frame rate,
+                // save the config
+                if (bestConfigMaxFps == Int.MAX_VALUE) {
+                    bestConfigMaxFps = currentConfigFrameRateCeiling
+                    bestSizes = possibleSizeList
+                } else if (bestConfigMaxFps < currentConfigFrameRateCeiling) {
+                    // only change the saved config if the max fps is better
+                    bestConfigMaxFps = currentConfigFrameRateCeiling
+                    bestSizes = possibleSizeList
+                }
             }
         }
-        if (suggestedStreamSpecMap == null) {
-            throw java.lang.IllegalArgumentException(
-                "No supported surface combination is found for camera device - Id : " +
-                    cameraId + " and Hardware level: " + hardwareLevel +
-                    ". May be the specified resolution is too large and not supported." +
-                    " Existing surfaces: " + existingSurfaces +
-                    " New configs: " + newUseCaseConfigs
+        require(bestSizes != null) {
+            "No supported surface combination is found for camera device - Id : $cameraId " +
+                "and Hardware level: $hardwareLevel. " +
+                "May be the specified resolution is too large and not supported. " +
+                "Existing surfaces: $attachedSurfaces. New configs: $newUseCaseConfigs."
+        }
+        return Pair(bestSizes, bestConfigMaxFps)
+    }
+
+    private fun generateSuggestedStreamSpecMap(
+        bestSizes: List<Size>,
+        targetFpsRange: Range<Int>?,
+        bestConfigMaxFps: Int,
+        newUseCaseConfigs: List<UseCaseConfig<*>>,
+        useCasesPriorityOrder: List<Int>,
+        resolvedDynamicRanges: Map<UseCaseConfig<*>, DynamicRange>,
+    ): Map<UseCaseConfig<*>, StreamSpec> {
+        val suggestedStreamSpecMap = mutableMapOf<UseCaseConfig<*>, StreamSpec>()
+        var targetFrameRateForDevice: Range<Int>? = null
+        if (targetFpsRange != null) {
+            targetFrameRateForDevice = getClosestSupportedDeviceFrameRate(
+                targetFpsRange,
+                bestConfigMaxFps
             )
         }
-        return Pair.create(suggestedStreamSpecMap, mapOf<AttachedSurfaceInfo, StreamSpec>())
+        for ((index, useCaseConfig) in newUseCaseConfigs.withIndex()) {
+            val resolutionForUseCase =
+                bestSizes[
+                    useCasesPriorityOrder.indexOf(index)]
+            val streamSpecBuilder = StreamSpec.builder(resolutionForUseCase)
+                .setDynamicRange(
+                    checkNotNull(resolvedDynamicRanges[useCaseConfig])
+                )
+            if (targetFrameRateForDevice != null) {
+                streamSpecBuilder.setExpectedFrameRateRange(targetFrameRateForDevice)
+            }
+            suggestedStreamSpecMap[useCaseConfig] = streamSpecBuilder.build()
+        }
+        return suggestedStreamSpecMap
+    }
+
+    private fun getRequiredMaxBitDepth(
+        resolvedDynamicRanges: Map<UseCaseConfig<*>, DynamicRange>
+    ): Int {
+        for (dynamicRange in resolvedDynamicRanges.values) {
+            if (dynamicRange.bitDepth == DynamicRange.BIT_DEPTH_10_BIT) {
+                return DynamicRange.BIT_DEPTH_10_BIT
+            }
+        }
+        return DynamicRange.BIT_DEPTH_8_BIT
+    }
+
+    private fun getSurfaceConfigList(
+        @CameraMode.Mode cameraMode: Int,
+        attachedSurfaces: List<AttachedSurfaceInfo>,
+        possibleSizeList: List<Size>,
+        newUseCaseConfigs: List<UseCaseConfig<*>>,
+        useCasesPriorityOrder: List<Int>,
+    ): List<SurfaceConfig> {
+        val surfaceConfigList: MutableList<SurfaceConfig> = mutableListOf()
+        for (attachedSurfaceInfo in attachedSurfaces) {
+            surfaceConfigList.add(attachedSurfaceInfo.surfaceConfig)
+        }
+
+        // Attach SurfaceConfig of new use cases
+        for ((i, size) in possibleSizeList.withIndex()) {
+            val newUseCase = newUseCaseConfigs[useCasesPriorityOrder[i]]
+            val imageFormat = newUseCase.inputFormat
+            // add new use case/size config to list of surfaces
+            val surfaceConfig = SurfaceConfig.transformSurfaceConfig(
+                cameraMode,
+                imageFormat,
+                size,
+                getUpdatedSurfaceSizeDefinitionByFormat(imageFormat)
+            )
+            surfaceConfigList.add(surfaceConfig)
+        }
+        return surfaceConfigList
+    }
+
+    private fun getCurrentConfigFrameRateCeiling(
+        possibleSizeList: List<Size>,
+        newUseCaseConfigs: List<UseCaseConfig<*>>,
+        useCasesPriorityOrder: List<Int>,
+        currentConfigFrameRateCeiling: Int,
+    ): Int {
+        var newConfigFrameRateCeiling: Int = currentConfigFrameRateCeiling
+        // Attach SurfaceConfig of new use cases
+        for ((i, size) in possibleSizeList.withIndex()) {
+            val newUseCase = newUseCaseConfigs[useCasesPriorityOrder[i]]
+            // get the maximum fps of the new surface and update the maximum fps of the
+            // proposed configuration
+            newConfigFrameRateCeiling = getUpdatedMaximumFps(
+                newConfigFrameRateCeiling,
+                newUseCase.inputFormat,
+                size
+            )
+        }
+        return newConfigFrameRateCeiling
+    }
+
+    private fun getMaxFrameRate(
+        imageFormat: Int,
+        size: Size?
+    ): Int {
+        var maxFrameRate = 0
+        try {
+            val minFrameDuration = getStreamConfigurationMapCompat().getOutputMinFrameDuration(
+                imageFormat,
+                size
+            ) ?: return 0
+            maxFrameRate = floor(1_000_000_000.0 / minFrameDuration + 0.05).toInt()
+        } catch (e1: IllegalArgumentException) {
+            // TODO: this try catch is in place for the rare that a surface config has a size
+            //  incompatible for getOutputMinFrameDuration...  put into a Quirk
+        }
+        return maxFrameRate
+    }
+
+    /**
+     *
+     * @param range
+     * @return the length of the range
+     */
+    private fun getRangeLength(range: Range<Int>): Int {
+        return range.upper - range.lower + 1
+    }
+
+    /**
+     * @return the distance between the nearest limits of two non-intersecting ranges
+     */
+    private fun getRangeDistance(firstRange: Range<Int>, secondRange: Range<Int>): Int {
+        require(
+            !firstRange.contains(secondRange.upper) &&
+                !firstRange.contains(secondRange.lower)
+        ) { "Ranges must not intersect" }
+        return if (firstRange.lower > secondRange.upper) {
+            firstRange.lower - secondRange.upper
+        } else {
+            secondRange.lower - firstRange.upper
+        }
+    }
+
+    /**
+     * @param targetFps the target frame rate range used while comparing to device-supported ranges
+     * @param storedRange the device-supported range that is currently saved and intersects with
+     * targetFps
+     * @param newRange a new potential device-supported range that intersects with targetFps
+     * @return the device-supported range that better matches the target fps
+     */
+    private fun compareIntersectingRanges(
+        targetFps: Range<Int>,
+        storedRange: Range<Int>,
+        newRange: Range<Int>
+    ): Range<Int> {
+        // TODO(b/272075984): some ranges may may have a larger intersection but may also have an
+        //  excessively large portion that is non-intersecting. Will want to do further
+        //  investigation to find a more optimized way to decide when a potential range has too
+        //  much non-intersecting value and discard it
+        val storedIntersectionsize =
+            getRangeLength(storedRange.intersect(targetFps)).toDouble()
+        val newIntersectionSize = getRangeLength(newRange.intersect(targetFps)).toDouble()
+        val newRangeRatio = newIntersectionSize / getRangeLength(newRange)
+        val storedRangeRatio = storedIntersectionsize / getRangeLength(storedRange)
+        if (newIntersectionSize > storedIntersectionsize) {
+            // if new, the new range must have at least 50% of its range intersecting, OR has a
+            // larger percentage of intersection than the previous stored range
+            if (newRangeRatio >= .5 || newRangeRatio >= storedRangeRatio) {
+                return newRange
+            }
+        } else if (newIntersectionSize == storedIntersectionsize) {
+            // if intersecting ranges have same length... pick the one that has the higher
+            // intersection ratio
+            if (newRangeRatio > storedRangeRatio) {
+                return newRange
+            } else if (newRangeRatio == storedRangeRatio && newRange.lower > storedRange.lower
+            ) {
+                // if equal intersection size AND ratios pick the higher range
+                return newRange
+            }
+        } else if (storedRangeRatio < .5 && newRangeRatio > storedRangeRatio
+        ) {
+            // if the new one has a smaller range... only change if existing has an intersection
+            // ratio < 50% and the new one has an intersection ratio > than the existing one
+            return newRange
+        }
+        return storedRange
+    }
+
+    /**
+     * Finds a frame rate range supported by the device that is closest to the target frame rate
+     *
+     * @param targetFrameRate the Target Frame Rate resolved from all current existing surfaces
+     * and incoming new use cases
+     * @return a frame rate range supported by the device that is closest to targetFrameRate
+     */
+    private fun getClosestSupportedDeviceFrameRate(
+        targetFrameRate: Range<Int>,
+        maxFps: Int
+    ): Range<Int> {
+        var newTargetFrameRate = targetFrameRate
+        // get all fps ranges supported by device
+        val availableFpsRanges =
+            cameraMetadata[CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES]
+                ?: return StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED
+        // if  whole target frame rate range > maxFps of configuration, the target for this
+        // calculation will be [max,max].
+
+        // if the range is partially larger than  maxFps, the target for this calculation will be
+        // [target.lower, max] for the sake of this calculation
+        newTargetFrameRate = Range(
+            min(newTargetFrameRate.lower, maxFps),
+            min(newTargetFrameRate.upper, maxFps)
+        )
+        var bestRange = StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED
+        var currentIntersectSize = 0
+        for (potentialRange in availableFpsRanges) {
+            // ignore ranges completely larger than configuration's maximum fps
+            if (maxFps < potentialRange.lower) {
+                continue
+            }
+            if (bestRange == StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED) {
+                bestRange = potentialRange
+            }
+            // take if range is a perfect match
+            if (potentialRange == newTargetFrameRate) {
+                bestRange = potentialRange
+                break
+            }
+            try {
+                // bias towards a range that intersects on the upper end
+                val newIntersection = potentialRange.intersect(newTargetFrameRate)
+                val newIntersectSize: Int = getRangeLength(
+                    newIntersection
+                )
+                // if this range intersects our target + no other range was already
+                if (currentIntersectSize == 0) {
+                    bestRange = potentialRange
+                    currentIntersectSize = newIntersectSize
+                } else if (newIntersectSize >= currentIntersectSize) {
+                    // if the currently stored range + new range both intersect, check to see
+                    // which one should be picked over the other
+                    bestRange = compareIntersectingRanges(
+                        newTargetFrameRate, bestRange,
+                        potentialRange
+                    )
+                    currentIntersectSize = getRangeLength(
+                        newTargetFrameRate.intersect(bestRange)
+                    )
+                }
+            } catch (e: IllegalArgumentException) {
+                if (currentIntersectSize != 0) {
+                    continue
+                }
+
+                // if no intersection is present, pick the range that is closer to our target
+                if (getRangeDistance(potentialRange, newTargetFrameRate)
+                    < getRangeDistance(
+                        bestRange, newTargetFrameRate
+                    )
+                ) {
+                    bestRange = potentialRange
+                } else if (getRangeDistance(potentialRange, newTargetFrameRate) ==
+                    getRangeDistance(bestRange, newTargetFrameRate)
+                ) {
+                    if (potentialRange.lower > bestRange.upper) {
+                        // if they both have the same distance, pick the higher range
+                        bestRange = potentialRange
+                    } else if (getRangeLength(potentialRange)
+                        < getRangeLength(bestRange)
+                    ) {
+                        // if one isn't higher than the other, pick the range with the
+                        // shorter length
+                        bestRange = potentialRange
+                    }
+                }
+            }
+        }
+        return bestRange
+    }
+
+    /**
+     * @param newTargetFrameRate    an incoming frame rate range
+     * @param storedTargetFrameRate a stored frame rate range to be modified
+     * @return adjusted target frame rate
+     *
+     * If the two ranges are both nonnull and disjoint of each other, then the range that was
+     * already stored will be used
+     */
+    private fun getUpdatedTargetFrameRate(
+        newTargetFrameRate: Range<Int>?,
+        storedTargetFrameRate: Range<Int>?
+    ): Range<Int>? {
+        var updatedTarget = storedTargetFrameRate
+        if (storedTargetFrameRate == null) {
+            // if stored value was null before, set it to the new value
+            updatedTarget = newTargetFrameRate
+        } else if (newTargetFrameRate != null) {
+            updatedTarget = try {
+                // get intersection of existing target fps
+                storedTargetFrameRate
+                    .intersect(newTargetFrameRate)
+            } catch (e: java.lang.IllegalArgumentException) {
+                // no intersection, keep the previously stored value
+                storedTargetFrameRate
+            }
+        }
+        return updatedTarget
+    }
+
+    /**
+     * @param currentMaxFps the previously stored Max FPS
+     * @param imageFormat   the image format of the incoming surface
+     * @param size          the size of the incoming surface
+     */
+    private fun getUpdatedMaximumFps(currentMaxFps: Int, imageFormat: Int, size: Size): Int {
+        return min(currentMaxFps, getMaxFrameRate(imageFormat, size))
     }
 
     /**
@@ -317,7 +738,7 @@
         imageFormat: Int
     ): List<Size> {
         // Applies TargetAspectRatio workaround
-        var ratio: Rational? =
+        val ratio: Rational? =
             when (targetAspectRatio[cameraMetadata, streamConfigurationMapCompat]) {
                 TargetAspectRatio.RATIO_4_3 ->
                     AspectRatioUtil.ASPECT_RATIO_4_3
@@ -426,6 +847,12 @@
         )
     }
 
+    private fun generate10BitSupportedCombinationList() {
+        surfaceCombinations10Bit.addAll(
+            GuaranteedConfigurationsUtil.get10BitSupportedCombinationList()
+        )
+    }
+
     /**
      * Generation the size definition for VGA, s720p, PREVIEW, s1440p, RECORD, MAXIMUM and
      * ULTRA_MAXIMUM.
@@ -621,42 +1048,14 @@
     }
 
     /**
-     * Retrieves the display which has the max size among all displays.
-     */
-    private fun getMaxSizeDisplay(): Display {
-        val displays: Array<Display> = displayManager.displays
-        if (displays.size == 1) {
-            return displays[0]
-        }
-        var maxDisplay: Display? = null
-        var maxDisplaySize = -1
-        for (display: Display in displays) {
-            if (display.state != Display.STATE_OFF) {
-                val displaySize = Point()
-                display.getRealSize(displaySize)
-                if (displaySize.x * displaySize.y > maxDisplaySize) {
-                    maxDisplaySize = displaySize.x * displaySize.y
-                    maxDisplay = display
-                }
-            }
-        }
-        if (maxDisplay == null) {
-            throw IllegalArgumentException(
-                "No display can be found from the input display manager!"
-            )
-        }
-        return maxDisplay
-    }
-
-    /**
      * Once the stream resource is occupied by one use case, it will impact the other use cases.
      * Therefore, we need to define the priority for stream resource usage. For the use cases
      * with the higher priority, we will try to find the best one for them in priority as
      * possible.
      */
     private fun getUseCasesPriorityOrder(newUseCaseConfigs: List<UseCaseConfig<*>>): List<Int> {
-        val priorityOrder: MutableList<Int> = ArrayList()
-        val priorityValueList: MutableList<Int> = ArrayList()
+        val priorityOrder: MutableList<Int> = mutableListOf()
+        val priorityValueList: MutableList<Int> = mutableListOf()
         for (config in newUseCaseConfigs) {
             val priority = config.getSurfaceOccupancyPriority(0)
             if (!priorityValueList.contains(priority)) {
@@ -711,13 +1110,13 @@
 
         if (Build.VERSION.SDK_INT >= 23 && highResolutionIncluded) {
             val highResolutionOutputSizes = map?.getHighResolutionOutputSizes(imageFormat)
-            if (highResolutionOutputSizes != null && highResolutionOutputSizes.isNotEmpty()) {
+            if (!highResolutionOutputSizes.isNullOrEmpty()) {
                 maxHighResolutionSize =
                     Collections.max(highResolutionOutputSizes.asList(), compareSizesByArea)
             }
         }
 
-        return Collections.max(Arrays.asList(maxSize, maxHighResolutionSize), compareSizesByArea)
+        return Collections.max(listOf(maxSize, maxHighResolutionSize), compareSizesByArea)
     }
 
     /**
@@ -735,11 +1134,11 @@
         // supportedOutputSizes
         // for some use case
         require(totalArrangementsCount != 0) { "Failed to find supported resolutions." }
-        val allPossibleSizeArrangements: MutableList<MutableList<Size>> = ArrayList()
+        val allPossibleSizeArrangements: MutableList<MutableList<Size>> = mutableListOf()
 
         // Initialize allPossibleSizeArrangements for the following operations
         for (i in 0 until totalArrangementsCount) {
-            val sizeList: MutableList<Size> = ArrayList()
+            val sizeList: MutableList<Size> = mutableListOf()
             allPossibleSizeArrangements.add(sizeList)
         }
 
@@ -769,7 +1168,23 @@
         return allPossibleSizeArrangements
     }
 
-    companion object {
-        private const val TAG = "SupportedSurfaceCombination"
-    }
+    /**
+     * A collection of feature settings related to the Camera2 capabilities exposed by
+     * [CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES] and device features exposed
+     * by [PackageManager.hasSystemFeature].
+     *
+     * @param cameraMode The camera mode. This involves the following mapping of mode to features:
+     *           [CameraMode.CONCURRENT_CAMERA] -> [PackageManager.FEATURE_CAMERA_CONCURRENT]
+     *           [CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA] ->
+     *           [CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR]
+     * @param requiredMaxBitDepth The required maximum bit depth for any non-RAW stream attached to
+     *           the camera. A value of [DynamicRange.BIT_DEPTH_10_BIT] corresponds to the camera
+     *           capability
+     *           [CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT].
+     *
+     */
+    data class FeatureSettings(
+        @CameraMode.Mode val cameraMode: Int,
+        val requiredMaxBitDepth: Int
+    )
 }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompat.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompat.kt
new file mode 100644
index 0000000..4d1fddd
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompat.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat
+
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.params.DynamicRangeProfiles
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.core.checkApi
+import androidx.camera.core.DynamicRange
+
+/**
+ * Helper for accessing features in DynamicRangeProfiles in a backwards compatible fashion.
+ */
+@RequiresApi(21)
+class DynamicRangeProfilesCompat internal constructor(
+    private val mImpl: DynamicRangeProfilesCompatImpl
+) {
+    /**
+     * Returns a set of supported [DynamicRange] that can be referenced in a single
+     * capture request.
+     *
+     * For example if a particular 10-bit output capable device returns (STANDARD,
+     * HLG10, HDR10) as result from calling [getSupportedDynamicRanges] and
+     * [DynamicRangeProfiles.getProfileCaptureRequestConstraints]
+     * returns (STANDARD, HLG10) when given an argument
+     * of STANDARD. This means that the corresponding camera device will only accept and process
+     * capture requests that reference outputs configured using HDR10 dynamic range or
+     * alternatively some combination of STANDARD and HLG10. However trying to queue capture
+     * requests to outputs that reference both HDR10 and STANDARD/HLG10 will result in
+     * IllegalArgumentException.
+     *
+     * The list will be empty in case there are no constraints for the given dynamic range.
+     *
+     * @param dynamicRange The dynamic range that will be checked for constraints
+     * @return non-modifiable set of dynamic ranges
+     * @throws IllegalArgumentException If the dynamic range argument is not within the set
+     * returned by [getSupportedDynamicRanges].
+     */
+    fun getDynamicRangeCaptureRequestConstraints(
+        dynamicRange: DynamicRange
+    ): Set<DynamicRange> {
+        return mImpl.getDynamicRangeCaptureRequestConstraints(dynamicRange)
+    }
+
+    /**
+     * Returns a set of supported dynamic ranges.
+     *
+     * @return a non-modifiable set of dynamic ranges.
+     */
+    fun getSupportedDynamicRanges(): Set<DynamicRange> {
+        return mImpl.getSupportedDynamicRanges()
+    }
+
+    /**
+     * Checks whether a given dynamic range is suitable for latency sensitive use cases.
+     *
+     * Due to internal lookahead logic, camera outputs configured with some dynamic range
+     * profiles may experience additional latency greater than 3 buffers. Using camera outputs
+     * with such dynamic ranges for latency sensitive use cases such as camera preview is not
+     * recommended. Dynamic ranges that have such extra streaming delay are typically utilized for
+     * scenarios such as offscreen video recording.
+     *
+     * @param dynamicRange The dynamic range to check for extra latency
+     * @return `true` if the given profile is not suitable for latency sensitive use cases,
+     * `false` otherwise.
+     * @throws IllegalArgumentException If the dynamic range argument is not within the set
+     * returned by [getSupportedDynamicRanges].
+     */
+    fun isExtraLatencyPresent(dynamicRange: DynamicRange): Boolean {
+        return mImpl.isExtraLatencyPresent(dynamicRange)
+    }
+
+    /**
+     * Returns the underlying framework
+     * [DynamicRangeProfiles].
+     *
+     * @return the underlying [DynamicRangeProfiles] or
+     * `null` if the device doesn't support 10 bit dynamic range.
+     */
+    @RequiresApi(33)
+    fun toDynamicRangeProfiles(): DynamicRangeProfiles? {
+        checkApi(
+            33, "DynamicRangesCompat can only be " +
+                "converted to DynamicRangeProfiles on API 33 or higher."
+        )
+        return mImpl.unwrap()
+    }
+
+    internal interface DynamicRangeProfilesCompatImpl {
+        fun getDynamicRangeCaptureRequestConstraints(
+            dynamicRange: DynamicRange
+        ): Set<DynamicRange>
+
+        fun getSupportedDynamicRanges(): Set<DynamicRange>
+
+        fun isExtraLatencyPresent(dynamicRange: DynamicRange): Boolean
+        fun unwrap(): DynamicRangeProfiles?
+    }
+
+    companion object {
+        /**
+         * Returns a [DynamicRangeProfilesCompat] using the capabilities derived from the provided
+         * characteristics.
+         *
+         * @param cameraMetadata the metaData used to derive dynamic range information.
+         * @return a [DynamicRangeProfilesCompat] object.
+         */
+        fun fromCameraMetaData(
+            cameraMetadata: CameraMetadata
+        ): DynamicRangeProfilesCompat {
+            var rangesCompat: DynamicRangeProfilesCompat? = null
+            if (Build.VERSION.SDK_INT >= 33) {
+                rangesCompat = toDynamicRangesCompat(
+                    cameraMetadata[CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES]
+                )
+            }
+            return rangesCompat ?: DynamicRangeProfilesCompatBaseImpl.COMPAT_INSTANCE
+        }
+
+        /**
+         * Creates an instance from a framework [DynamicRangeProfiles]
+         * object.
+         *
+         * @param dynamicRangeProfiles a [DynamicRangeProfiles].
+         * @return an equivalent [DynamicRangeProfilesCompat] object.
+         */
+        @RequiresApi(33)
+        fun toDynamicRangesCompat(
+            dynamicRangeProfiles: DynamicRangeProfiles?
+        ): DynamicRangeProfilesCompat? {
+            if (dynamicRangeProfiles == null) {
+                return null
+            }
+            checkApi(
+                33, "DynamicRangeProfiles can only " +
+                    "be converted to DynamicRangesCompat on API 33 or higher."
+            )
+            return DynamicRangeProfilesCompat(
+                DynamicRangeProfilesCompatApi33Impl(
+                    dynamicRangeProfiles
+                )
+            )
+        }
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatApi33Impl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatApi33Impl.kt
new file mode 100644
index 0000000..12a5687
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatApi33Impl.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat
+
+import android.hardware.camera2.params.DynamicRangeProfiles
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.integration.internal.DynamicRangeConversions
+import androidx.camera.core.DynamicRange
+import java.util.Collections
+
+@RequiresApi(33)
+internal class DynamicRangeProfilesCompatApi33Impl(
+    private val dynamicRangeProfiles: DynamicRangeProfiles
+) : DynamicRangeProfilesCompat.DynamicRangeProfilesCompatImpl {
+
+    override fun getDynamicRangeCaptureRequestConstraints(
+        dynamicRange: DynamicRange
+    ): Set<DynamicRange> {
+        val dynamicRangeProfile = dynamicRangeToFirstSupportedProfile(dynamicRange)
+        require(dynamicRangeProfile != null) {
+            "DynamicRange is not supported: $dynamicRange"
+        }
+        return profileSetToDynamicRangeSet(
+            dynamicRangeProfiles.getProfileCaptureRequestConstraints(dynamicRangeProfile)
+        )
+    }
+
+    override fun getSupportedDynamicRanges() = profileSetToDynamicRangeSet(
+        dynamicRangeProfiles.supportedProfiles
+    )
+
+    override fun isExtraLatencyPresent(dynamicRange: DynamicRange): Boolean {
+        val dynamicRangeProfile = dynamicRangeToFirstSupportedProfile(dynamicRange)
+        require(
+            dynamicRangeProfile != null
+        ) {
+            "DynamicRange is not supported: $dynamicRange"
+        }
+        return dynamicRangeProfiles.isExtraLatencyPresent(dynamicRangeProfile)
+    }
+
+    override fun unwrap() = dynamicRangeProfiles
+
+    private fun dynamicRangeToFirstSupportedProfile(dynamicRange: DynamicRange) =
+        DynamicRangeConversions.dynamicRangeToFirstSupportedProfile(
+            dynamicRange,
+            dynamicRangeProfiles
+        )
+
+    private fun profileToDynamicRange(profile: Long): DynamicRange {
+        val result = DynamicRangeConversions.profileToDynamicRange(
+            profile
+        )
+        require(result != null) {
+            "Dynamic range profile cannot be converted to a DynamicRange object: $profile"
+        }
+        return result
+    }
+
+    private fun profileSetToDynamicRangeSet(profileSet: Set<Long>): Set<DynamicRange> {
+        if (profileSet.isEmpty()) {
+            return emptySet()
+        }
+        val dynamicRangeSet: MutableSet<DynamicRange> = mutableSetOf()
+        for (profile in profileSet) {
+            dynamicRangeSet.add(profileToDynamicRange(profile))
+        }
+        return Collections.unmodifiableSet(dynamicRangeSet)
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatBaseImpl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatBaseImpl.kt
new file mode 100644
index 0000000..b9a0cad
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatBaseImpl.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat
+
+import android.hardware.camera2.params.DynamicRangeProfiles
+import androidx.annotation.RequiresApi
+import androidx.camera.core.DynamicRange
+import androidx.core.util.Preconditions
+
+@RequiresApi(21)
+internal class DynamicRangeProfilesCompatBaseImpl :
+    DynamicRangeProfilesCompat.DynamicRangeProfilesCompatImpl {
+    override fun getDynamicRangeCaptureRequestConstraints(
+        dynamicRange: DynamicRange
+    ): Set<DynamicRange> {
+        Preconditions.checkArgument(
+            DynamicRange.SDR == dynamicRange,
+            "DynamicRange is not supported: $dynamicRange"
+        )
+        return SDR_ONLY
+    }
+
+    override fun getSupportedDynamicRanges(): Set<DynamicRange> {
+        return SDR_ONLY
+    }
+
+    override fun isExtraLatencyPresent(dynamicRange: DynamicRange): Boolean {
+        Preconditions.checkArgument(
+            DynamicRange.SDR == dynamicRange,
+            "DynamicRange is not supported: $dynamicRange"
+        )
+        return false
+    }
+
+    override fun unwrap(): DynamicRangeProfiles? {
+        return null
+    }
+
+    companion object {
+        val COMPAT_INSTANCE: DynamicRangeProfilesCompat =
+            DynamicRangeProfilesCompat(DynamicRangeProfilesCompatBaseImpl())
+        private val SDR_ONLY = setOf(DynamicRange.SDR)
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompat.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompat.kt
index 5b963d5..00ebe44 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompat.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompat.kt
@@ -136,6 +136,10 @@
         return outputSizes?.clone()
     }
 
+    fun getOutputMinFrameDuration(format: Int, size: Size?): Long? {
+        return impl.getOutputMinFrameDuration(format, size)
+    }
+
     /**
      * Returns the [StreamConfigurationMap] represented by this object.
      */
@@ -147,6 +151,7 @@
         fun getOutputSizes(format: Int): Array<Size>?
         fun <T> getOutputSizes(klass: Class<T>): Array<Size>?
         fun getHighResolutionOutputSizes(format: Int): Array<Size>?
+        fun getOutputMinFrameDuration(format: Int, size: Size?): Long?
 
         /**
          * Returns the underlying [StreamConfigurationMap] instance.
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatBaseImpl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatBaseImpl.kt
index b5bfa26..fef2df7 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatBaseImpl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatBaseImpl.kt
@@ -50,6 +50,10 @@
         return null
     }
 
+    override fun getOutputMinFrameDuration(format: Int, size: Size?): Long? {
+        return streamConfigurationMap?.getOutputMinFrameDuration(format, size)
+    }
+
     override fun unwrap(): StreamConfigurationMap? {
         return streamConfigurationMap
     }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/FlashTooSlowQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/FlashTooSlowQuirk.kt
index 9dfecbd..46ab296 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/FlashTooSlowQuirk.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/FlashTooSlowQuirk.kt
@@ -26,11 +26,12 @@
  * Quirks that denotes the device has a slow flash sequence that could result in blurred pictures.
  *
  * QuirkSummary
- * - Bug Id: 211474332, 286190938, 280221967
+ * - Bug Id: 211474332, 286190938, 280221967, 296814664, 296816175
  * - Description: When capturing still photos in auto flash mode, it needs more than 1 second to
  *   flash or capture actual photo after flash, and therefore it easily results in blurred or dark
  *   or overexposed pictures.
- * - Device(s): Pixel 3a / Pixel 3a XL, all models of Pixel 4 and 5, SM-A320
+ * - Device(s): Pixel 3a / Pixel 3a XL, all models of Pixel 4 and 5, SM-A320, Moto G20, Itel A48,
+ *   Realme C11 2021
  *
  * TODO(b/270421716): enable CameraXQuirksClassDetector lint check when kotlin is supported.
  */
@@ -44,7 +45,10 @@
             "PIXEL 3A XL",
             "PIXEL 4", // includes Pixel 4 XL, 4A, and 4A (5g) too
             "PIXEL 5", // includes Pixel 5A too
-            "SM-A320"
+            "SM-A320",
+            "MOTO G(20)",
+            "ITEL L6006", // Itel A48
+            "RMX3231" // Realme C11 2021
         )
 
         fun isEnabled(cameraMetadata: CameraMetadata): Boolean {
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/InvalidVideoProfilesQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/InvalidVideoProfilesQuirk.kt
index 4c2c27a..7df4b6c 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/InvalidVideoProfilesQuirk.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/InvalidVideoProfilesQuirk.kt
@@ -28,13 +28,14 @@
  * Quirk denoting the video profile list returns by [EncoderProfiles] is invalid.
  *
  * QuirkSummary
- * - Bug Id: 267727595, 278860860
- * - Description: When using [EncoderProfiles] on TP1A or TD1A builds of Android API 33,
+ * - Bug Id: 267727595, 278860860, 298951126, 298952500
+ * - Description: When using [EncoderProfiles] on some builds of Android API 33,
  *   [EncoderProfiles.getVideoProfiles] returns a list with size one, but the single value in the
  *   list is null. This is not the expected behavior, and makes [EncoderProfiles] lack of video
  *   information.
  * - Device(s): Pixel 4 and above pixel devices with TP1A or TD1A builds (API 33), Samsung devices
- *              with TP1A build (API 33).
+ *              with TP1A build (API 33), Xiaomi devices with TKQ1 build (API 33), OnePlus and Oppo
+ *              devices with API 33 build.
  *
  * TODO: enable CameraXQuirksClassDetector lint check when kotlin is supported.
  */
@@ -56,8 +57,19 @@
             "pixel 7 pro"
         )
 
+        private val AFFECTED_ONE_PLUS_MODELS: List<String> = listOf(
+            "cph2417",
+            "cph2451"
+        )
+
+        private val AFFECTED_OPPO_MODELS: List<String> = listOf(
+            "cph2437",
+            "cph2525"
+        )
+
         fun isEnabled(): Boolean {
-            return isAffectedSamsungDevices() || isAffectedPixelDevices()
+            return isAffectedSamsungDevices() || isAffectedPixelDevices() ||
+                isAffectedXiaomiDevices() || isAffectedOppoDevices() || isAffectedOnePlusDevices()
         }
 
         private fun isAffectedSamsungDevices(): Boolean {
@@ -68,12 +80,37 @@
             return isAffectedPixelModel() && isAffectedPixelBuild()
         }
 
+        private fun isAffectedXiaomiDevices(): Boolean {
+            return ("redmi".equals(Build.BRAND, true) || "xiaomi".equals(Build.BRAND, true)) &&
+                isTkq1Build()
+        }
+
+        private fun isAffectedOnePlusDevices(): Boolean {
+            return isAffectedOnePlusModel() && isAPI33()
+        }
+
+        private fun isAffectedOppoDevices(): Boolean {
+            return isAffectedOppoModel() && isAPI33()
+        }
+
         private fun isAffectedPixelModel(): Boolean {
             return AFFECTED_PIXEL_MODELS.contains(
                 Build.MODEL.lowercase()
             )
         }
 
+        private fun isAffectedOnePlusModel(): Boolean {
+            return AFFECTED_ONE_PLUS_MODELS.contains(
+                Build.MODEL.lowercase()
+            )
+        }
+
+        private fun isAffectedOppoModel(): Boolean {
+            return AFFECTED_OPPO_MODELS.contains(
+                Build.MODEL.lowercase()
+            )
+        }
+
         private fun isAffectedPixelBuild(): Boolean {
             return isTp1aBuild() || isTd1aBuild()
         }
@@ -85,5 +122,13 @@
         private fun isTd1aBuild(): Boolean {
             return Build.ID.startsWith("TD1A", true)
         }
+
+        private fun isTkq1Build(): Boolean {
+            return Build.ID.startsWith("TKQ1", true)
+        }
+
+        private fun isAPI33(): Boolean {
+            return Build.VERSION.SDK_INT == 33
+        }
     }
 }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/TorchIsClosedAfterImageCapturingQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/TorchIsClosedAfterImageCapturingQuirk.kt
index 553c96f..9f17c35 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/TorchIsClosedAfterImageCapturingQuirk.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/TorchIsClosedAfterImageCapturingQuirk.kt
@@ -25,7 +25,7 @@
  * QuirkSummary
  * - Bug Id: 228272227
  * - Description: The Torch is unexpectedly turned off after taking a picture.
- * - Device(s): Redmi 4X, Redmi 5A, Redmi Note 5, Mi A1, Mi A2, Mi A2 lite and Redmi 6 Pro.
+ * - Device(s): Redmi 4X, Redmi 5A, Redmi Note 5 (Pro), Mi A1, Mi A2, Mi A2 lite and Redmi 6 Pro.
  */
 @SuppressLint("CameraXQuirksClassDetector") // TODO(b/270421716): enable when kotlin is supported.
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
@@ -40,6 +40,7 @@
             "redmi 4x", // Xiaomi Redmi 4X
             "redmi 5a", // Xiaomi Redmi 5A
             "redmi note 5", // Xiaomi Redmi Note 5
+            "redmi note 5 pro", // Xiaomi Redmi Note 5 Pro
             "redmi 6 pro", // Xiaomi Redmi 6 Pro
         )
 
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
index 84bd53e..88ff62b 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -43,6 +43,7 @@
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraConfig
 import androidx.camera.camera2.pipe.integration.interop.Camera2CameraControl
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
+import androidx.camera.core.DynamicRange
 import androidx.camera.core.UseCase
 import androidx.camera.core.impl.CameraInternal
 import androidx.camera.core.impl.CameraMode
@@ -457,7 +458,12 @@
             )
         }
 
-        return supportedSurfaceCombination.checkSupported(CameraMode.DEFAULT, surfaceConfigs)
+        return supportedSurfaceCombination.checkSupported(
+            SupportedSurfaceCombination.FeatureSettings(
+                CameraMode.DEFAULT,
+                DynamicRange.BIT_DEPTH_8_BIT
+            ), surfaceConfigs
+        )
     }
 
     private fun Collection<UseCase>.surfaceCount(): Int =
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeConversions.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeConversions.kt
new file mode 100644
index 0000000..090e587
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeConversions.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.internal
+
+import android.hardware.camera2.params.DynamicRangeProfiles
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.camera.core.DynamicRange
+
+/**
+ * Utilities for converting between [DynamicRange] and profiles from
+ * [DynamicRangeProfiles].
+ */
+@RequiresApi(33)
+internal object DynamicRangeConversions {
+    private val PROFILE_TO_DR_MAP: MutableMap<Long, DynamicRange> = mutableMapOf()
+    private val DR_TO_PROFILE_MAP: MutableMap<DynamicRange?, List<Long>> = mutableMapOf()
+
+    init {
+        // SDR
+        PROFILE_TO_DR_MAP[DynamicRangeProfiles.STANDARD] = DynamicRange.SDR
+        DR_TO_PROFILE_MAP[DynamicRange.SDR] = listOf(DynamicRangeProfiles.STANDARD)
+
+        // HLG
+        PROFILE_TO_DR_MAP[DynamicRangeProfiles.HLG10] = DynamicRange.HLG_10_BIT
+        DR_TO_PROFILE_MAP[PROFILE_TO_DR_MAP[DynamicRangeProfiles.HLG10]] =
+            listOf(DynamicRangeProfiles.HLG10)
+
+        // HDR10
+        PROFILE_TO_DR_MAP[DynamicRangeProfiles.HDR10] = DynamicRange.HDR10_10_BIT
+        DR_TO_PROFILE_MAP[DynamicRange.HDR10_10_BIT] = listOf(DynamicRangeProfiles.HDR10)
+
+        // HDR10+
+        PROFILE_TO_DR_MAP[DynamicRangeProfiles.HDR10_PLUS] = DynamicRange.HDR10_PLUS_10_BIT
+        DR_TO_PROFILE_MAP[DynamicRange.HDR10_PLUS_10_BIT] = listOf(DynamicRangeProfiles.HDR10_PLUS)
+
+        // Dolby Vision 10-bit
+        // A list of the Camera2 10-bit dolby vision profiles ordered by priority. Any API that
+        // takes a DynamicRange with dolby vision encoding will attempt to convert to these
+        // profiles in order, using the first one that is supported. We will need to add a
+        // mechanism for choosing between these
+        val dolbyVision10BitProfilesOrdered = listOf(
+            DynamicRangeProfiles.DOLBY_VISION_10B_HDR_OEM,
+            DynamicRangeProfiles.DOLBY_VISION_10B_HDR_OEM_PO,
+            DynamicRangeProfiles.DOLBY_VISION_10B_HDR_REF,
+            DynamicRangeProfiles.DOLBY_VISION_10B_HDR_REF_PO
+        )
+        for (profile in dolbyVision10BitProfilesOrdered) {
+            PROFILE_TO_DR_MAP[profile] = DynamicRange.DOLBY_VISION_10_BIT
+        }
+        DR_TO_PROFILE_MAP[DynamicRange.DOLBY_VISION_10_BIT] =
+            dolbyVision10BitProfilesOrdered
+
+        // Dolby vision 8-bit
+        val dolbyVision8BitProfilesOrdered = listOf(
+            DynamicRangeProfiles.DOLBY_VISION_8B_HDR_OEM,
+            DynamicRangeProfiles.DOLBY_VISION_8B_HDR_OEM_PO,
+            DynamicRangeProfiles.DOLBY_VISION_8B_HDR_REF,
+            DynamicRangeProfiles.DOLBY_VISION_8B_HDR_REF_PO
+        )
+        for (profile in dolbyVision8BitProfilesOrdered) {
+            PROFILE_TO_DR_MAP[profile] = DynamicRange.DOLBY_VISION_8_BIT
+        }
+        DR_TO_PROFILE_MAP[DynamicRange.DOLBY_VISION_8_BIT] =
+            dolbyVision8BitProfilesOrdered
+    }
+
+    /**
+     * Converts Camera2 dynamic range profile constants to [DynamicRange].
+     */
+    @DoNotInline
+    fun profileToDynamicRange(profile: Long): DynamicRange? {
+        return PROFILE_TO_DR_MAP[profile]
+    }
+
+    /**
+     * Converts a [DynamicRange] to a Camera2 dynamic range profile.
+     *
+     *
+     * For dynamic ranges which can resolve to multiple profiles, the first supported profile
+     * from the passed [android.hardware.camera2.params.DynamicRangeProfiles] will be
+     * returned. The order in which profiles are checked for support is internally defined.
+     *
+     *
+     * This will only return profiles for fully defined dynamic ranges. For instance, if the
+     * format returned by [DynamicRange.getEncoding] is
+     * [DynamicRange.ENCODING_HDR_UNSPECIFIED], this will return `null`.
+     */
+    @DoNotInline
+    fun dynamicRangeToFirstSupportedProfile(
+        dynamicRange: DynamicRange,
+        dynamicRangeProfiles: DynamicRangeProfiles
+    ): Long? {
+        val orderedProfiles = DR_TO_PROFILE_MAP[dynamicRange]
+        if (orderedProfiles != null) {
+            val supportedList = dynamicRangeProfiles.supportedProfiles
+            for (profile in orderedProfiles) {
+                if (supportedList.contains(profile)) {
+                    return profile
+                }
+            }
+        }
+
+        // No profile supported
+        return null
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeResolver.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeResolver.kt
new file mode 100644
index 0000000..947d31c
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeResolver.kt
@@ -0,0 +1,479 @@
+package androidx.camera.camera2.pipe.integration.internal
+
+import android.hardware.camera2.CameraCharacteristics
+import android.os.Build
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.integration.compat.DynamicRangeProfilesCompat
+import androidx.camera.core.DynamicRange
+import androidx.camera.core.impl.AttachedSurfaceInfo
+import androidx.camera.core.impl.UseCaseConfig
+import androidx.core.util.Preconditions
+
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+class DynamicRangeResolver(val cameraMetadata: CameraMetadata) {
+    private val is10BitSupported: Boolean
+    private val dynamicRangesInfo: DynamicRangeProfilesCompat
+
+    init {
+        val availableCapabilities: IntArray? =
+            cameraMetadata[CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES]
+        is10BitSupported =
+            availableCapabilities?.contains(
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT
+            )
+                ?: false
+        dynamicRangesInfo = DynamicRangeProfilesCompat.fromCameraMetaData(cameraMetadata)
+    }
+
+    /**
+     * Returns whether 10-bit dynamic ranges are supported on this device.
+     */
+    fun is10BitDynamicRangeSupported(): Boolean = is10BitSupported
+
+    /**
+     * Returns a set of supported dynamic ranges for the dynamic ranges requested by the list of
+     * attached and new use cases.
+     *
+     *
+     * If a new use case requests a dynamic range that isn't supported, an
+     * IllegalArgumentException will be thrown.
+     */
+    fun resolveAndValidateDynamicRanges(
+        existingSurfaces: List<AttachedSurfaceInfo>,
+        newUseCaseConfigs: List<UseCaseConfig<*>>,
+        useCasePriorityOrder: List<Int>
+    ): Map<UseCaseConfig<*>, DynamicRange> {
+        // Create an ordered set of already-attached surface's dynamic ranges. These are assumed
+        // to be valid since they are already attached.
+        val orderedExistingDynamicRanges = mutableSetOf<DynamicRange>()
+        for (asi in existingSurfaces) {
+            orderedExistingDynamicRanges.add(asi.dynamicRange)
+        }
+
+        // Get the supported dynamic ranges from the device
+        val supportedDynamicRanges = dynamicRangesInfo.getSupportedDynamicRanges()
+
+        // Collect initial dynamic range constraints. This set will potentially shrink as we add
+        // more dynamic ranges. We start with the initial set of supported dynamic ranges to
+        // denote no constraints.
+        val combinedConstraints = supportedDynamicRanges.toMutableSet()
+        for (dynamicRange in orderedExistingDynamicRanges) {
+            updateConstraints(combinedConstraints, dynamicRange, dynamicRangesInfo)
+        }
+
+        // We want to resolve and validate dynamic ranges in the following order:
+        // 1. First validate fully defined dynamic ranges. No resolving is required here.
+        // 2. Resolve and validate partially defined dynamic ranges, such as HDR_UNSPECIFIED or
+        // dynamic ranges with concrete encodings but BIT_DEPTH_UNSPECIFIED. We can now potentially
+        // infer a dynamic range based on constraints of the fully defined dynamic ranges or
+        // the list of supported HDR dynamic ranges.
+        // 3. Finally, resolve and validate UNSPECIFIED dynamic ranges. These will resolve
+        // to dynamic ranges from the first 2 groups, or fall back to SDR if no other dynamic
+        // ranges are defined.
+        //
+        // To accomplish this, we need to partition the use cases into 3 categories.
+        val orderedFullyDefinedUseCaseConfigs: MutableList<UseCaseConfig<*>> = mutableListOf()
+        val orderedPartiallyDefinedUseCaseConfigs: MutableList<UseCaseConfig<*>> = mutableListOf()
+        val orderedUndefinedUseCaseConfigs: MutableList<UseCaseConfig<*>> = mutableListOf()
+        for (priorityIdx in useCasePriorityOrder) {
+            val config = newUseCaseConfigs[priorityIdx]
+            val requestedDynamicRange = config.dynamicRange
+            if (isFullyUnspecified(requestedDynamicRange)
+            ) {
+                orderedUndefinedUseCaseConfigs.add(config)
+            } else if (isPartiallySpecified(requestedDynamicRange)
+            ) {
+                orderedPartiallyDefinedUseCaseConfigs.add(config)
+            } else {
+                orderedFullyDefinedUseCaseConfigs.add(config)
+            }
+        }
+        val resolvedDynamicRanges: MutableMap<UseCaseConfig<*>, DynamicRange> = mutableMapOf()
+        // Keep track of new dynamic ranges for more fine-grained error messages in exceptions.
+        // This allows us to differentiate between dynamic ranges from already-attached use cases
+        // and requested dynamic ranges from newly added use cases.
+        val orderedNewDynamicRanges: MutableSet<DynamicRange> = mutableSetOf()
+        // Now resolve and validate all of the dynamic ranges in order of the 3 partitions form
+        // above.
+        val orderedUseCaseConfigs: MutableList<UseCaseConfig<*>> = mutableListOf()
+        orderedUseCaseConfigs.addAll(orderedFullyDefinedUseCaseConfigs)
+        orderedUseCaseConfigs.addAll(orderedPartiallyDefinedUseCaseConfigs)
+        orderedUseCaseConfigs.addAll(orderedUndefinedUseCaseConfigs)
+        for (config in orderedUseCaseConfigs) {
+            val resolvedDynamicRange: DynamicRange = resolveDynamicRangeAndUpdateConstraints(
+                supportedDynamicRanges, orderedExistingDynamicRanges,
+                orderedNewDynamicRanges, config, combinedConstraints
+            )
+            resolvedDynamicRanges[config] = resolvedDynamicRange
+            if (!orderedExistingDynamicRanges.contains(resolvedDynamicRange)) {
+                orderedNewDynamicRanges.add(resolvedDynamicRange)
+            }
+        }
+        return resolvedDynamicRanges
+    }
+
+    private fun resolveDynamicRangeAndUpdateConstraints(
+        supportedDynamicRanges: Set<DynamicRange?>,
+        orderedExistingDynamicRanges: Set<DynamicRange>,
+        orderedNewDynamicRanges: Set<DynamicRange>,
+        config: UseCaseConfig<*>,
+        outCombinedConstraints: MutableSet<DynamicRange>
+    ): DynamicRange {
+        val requestedDynamicRange = config.dynamicRange
+        val resolvedDynamicRange: DynamicRange? = resolveDynamicRange(
+            requestedDynamicRange,
+            outCombinedConstraints, orderedExistingDynamicRanges, orderedNewDynamicRanges,
+            config.targetName
+        )
+        if (resolvedDynamicRange != null) {
+            updateConstraints(outCombinedConstraints, resolvedDynamicRange, dynamicRangesInfo)
+        } else {
+            throw IllegalArgumentException(
+                "Unable to resolve supported " +
+                    "dynamic range. The dynamic range may not be supported on the device " +
+                    "or may not be allowed concurrently with other attached use cases.\n" +
+                    "Use case:\n" +
+                    "  ${config.targetName}\n" +
+                    "Requested dynamic range:\n" +
+                    "  $requestedDynamicRange\n" +
+                    "Supported dynamic ranges:\n" +
+                    "  $supportedDynamicRanges\n" +
+                    "Constrained set of concurrent dynamic ranges:\n" +
+                    "  $outCombinedConstraints",
+            )
+        }
+        return resolvedDynamicRange
+    }
+
+    /**
+     * Resolves the requested dynamic range into a fully specified dynamic range.
+     *
+     *
+     * This uses existing fully-specified dynamic ranges, new fully-specified dynamic ranges,
+     * dynamic range constraints and the list of supported dynamic ranges to exhaustively search
+     * for a dynamic range if the requested dynamic range is not fully specified, i.e., it has an
+     * UNSPECIFIED encoding or UNSPECIFIED bitrate.
+     *
+     *
+     * Any dynamic range returned will be validated to work according to the constraints and
+     * supported dynamic ranges provided.
+     *
+     *
+     * If no suitable dynamic range can be found, returns `null`.
+     */
+    private fun resolveDynamicRange(
+        requestedDynamicRange: DynamicRange,
+        combinedConstraints: Set<DynamicRange>,
+        orderedExistingDynamicRanges: Set<DynamicRange>,
+        orderedNewDynamicRanges: Set<DynamicRange>,
+        rangeOwnerLabel: String
+    ): DynamicRange? {
+
+        // Dynamic range is already resolved if it is fully specified.
+        if (requestedDynamicRange.isFullySpecified) {
+            return if (combinedConstraints.contains(requestedDynamicRange)) {
+                requestedDynamicRange
+            } else null
+            // Requested dynamic range is full specified but unsupported. No need to continue
+            // trying to resolve.
+        }
+
+        // Explicitly handle the case of SDR with unspecified bit depth.
+        // SDR is only supported as 8-bit.
+        val requestedEncoding = requestedDynamicRange.encoding
+        val requestedBitDepth = requestedDynamicRange.bitDepth
+        if (requestedEncoding == DynamicRange.ENCODING_SDR &&
+            requestedBitDepth == DynamicRange.BIT_DEPTH_UNSPECIFIED
+        ) {
+            return if (combinedConstraints.contains(DynamicRange.SDR)) {
+                DynamicRange.SDR
+            } else null
+            // If SDR isn't supported, we can't resolve to any other dynamic range.
+        }
+
+        // First attempt to find another fully specified HDR dynamic range to resolve to from
+        // existing dynamic ranges
+        var resolvedDynamicRange = findSupportedHdrMatch(
+            requestedDynamicRange,
+            orderedExistingDynamicRanges, combinedConstraints
+        )
+        if (resolvedDynamicRange != null) {
+            Log.debug {
+                "DynamicRangeResolver: Resolved dynamic range for use case $rangeOwnerLabel " +
+                    "from existing attached surface.\n" +
+                    "$requestedDynamicRange\n->\n$resolvedDynamicRange"
+            }
+
+            return resolvedDynamicRange
+        }
+
+        // Attempt to find another fully specified HDR dynamic range to resolve to from
+        // new dynamic ranges
+        resolvedDynamicRange =
+            findSupportedHdrMatch(
+                requestedDynamicRange,
+                orderedNewDynamicRanges, combinedConstraints
+            )
+        if (resolvedDynamicRange != null) {
+            Log.debug {
+                "DynamicRangeResolver: Resolved dynamic range for use case $rangeOwnerLabel from " +
+                    "concurrently bound use case." +
+                    "\n$requestedDynamicRange\n->\n$resolvedDynamicRange"
+            }
+
+            return resolvedDynamicRange
+        }
+
+        // Now that we have checked existing HDR dynamic ranges, we must resolve fully unspecified
+        // and unspecified 8-bit dynamic ranges to SDR if it is supported. This ensures the
+        // default behavior for most use cases is to choose SDR when an HDR dynamic range isn't
+        // already present or explicitly requested.
+        if (canResolveWithinConstraints(
+                requestedDynamicRange, DynamicRange.SDR,
+                combinedConstraints
+            )
+        ) {
+            Log.debug {
+                "DynamicRangeResolver: Resolved dynamic range for use case $rangeOwnerLabel to " +
+                    "no compatible HDR dynamic ranges.\n$requestedDynamicRange\n" +
+                    "->\n${DynamicRange.SDR}"
+            }
+            return DynamicRange.SDR
+        }
+
+        // For unspecified HDR encodings (10-bit or unspecified bit depth), we have a
+        // couple options: the device recommended 10-bit encoding or the mandated HLG encoding.
+        if (requestedEncoding == DynamicRange.ENCODING_HDR_UNSPECIFIED &&
+            ((requestedBitDepth == DynamicRange.BIT_DEPTH_10_BIT ||
+                requestedBitDepth == DynamicRange.BIT_DEPTH_UNSPECIFIED))
+        ) {
+            val hdrDefaultRanges: MutableSet<DynamicRange> = mutableSetOf()
+
+            // Attempt to use the recommended 10-bit dynamic range
+            var recommendedRange: DynamicRange? = null
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+                recommendedRange =
+                    Api33Impl.getRecommended10BitDynamicRange(
+                        cameraMetadata
+                    )
+                if (recommendedRange != null) {
+                    hdrDefaultRanges.add(recommendedRange)
+                }
+            }
+            // Attempt to fall back to HLG since it is a mandated required 10-bit
+            // dynamic range.
+            hdrDefaultRanges.add(DynamicRange.HLG_10_BIT)
+            resolvedDynamicRange =
+                findSupportedHdrMatch(
+                    requestedDynamicRange, hdrDefaultRanges, combinedConstraints
+                )
+            if (resolvedDynamicRange != null) {
+                Log.debug {
+                    "DynamicRangeResolver: Resolved dynamic range for use case $rangeOwnerLabel" +
+                        "from ${
+                            if ((resolvedDynamicRange == recommendedRange)) "recommended"
+                            else "required"
+                        } 10-bit supported dynamic range.\n" +
+                        "${requestedDynamicRange}\n" +
+                        "->\n" +
+                        "$resolvedDynamicRange"
+                }
+                return resolvedDynamicRange
+            }
+        }
+
+        // Finally, attempt to find an HDR dynamic range for HDR or 10-bit dynamic ranges from
+        // the constraints of the other validated dynamic ranges. If there are no other dynamic
+        // ranges, this should be the full list of supported dynamic ranges.
+        // The constraints are unordered, so it may not produce an "optimal" dynamic range. This
+        // works for 8-bit, 10-bit or partially specified HDR dynamic ranges.
+        for (candidateRange: DynamicRange in combinedConstraints) {
+            check(candidateRange.isFullySpecified) {
+                "Candidate dynamic range must be fully specified."
+            }
+
+            // Only consider HDR constraints
+            if ((candidateRange == DynamicRange.SDR)) {
+                continue
+            }
+            if (canResolveDynamicRange(
+                    requestedDynamicRange,
+                    candidateRange
+                )
+            ) {
+                Log.debug {
+                    "DynamicRangeResolver: Resolved dynamic range for use case $rangeOwnerLabel " +
+                        "from validated dynamic range constraints or supported HDR dynamic " +
+                        "ranges.\n$requestedDynamicRange\n->\n$candidateRange"
+                }
+                return candidateRange
+            }
+        }
+
+        // Unable to resolve dynamic range
+        return null
+    }
+
+    /**
+     * Updates the provided dynamic range constraints by combining them with the new constraints
+     * from the new dynamic range.
+     *
+     * @param combinedConstraints The constraints that will be updated. This set must not be empty.
+     * @param newDynamicRange     The new dynamic range for which we'll apply new constraints
+     * @param dynamicRangesInfo   Information about dynamic ranges to retrieve new constraints.
+     */
+    private fun updateConstraints(
+        combinedConstraints: MutableSet<DynamicRange>,
+        newDynamicRange: DynamicRange,
+        dynamicRangesInfo: DynamicRangeProfilesCompat
+    ) {
+        Preconditions.checkState(
+            combinedConstraints.isNotEmpty(), "Cannot update already-empty constraints."
+        )
+        val newConstraints =
+            dynamicRangesInfo.getDynamicRangeCaptureRequestConstraints(newDynamicRange)
+        if (newConstraints.isNotEmpty()) {
+            // Retain for potential exception message
+            val previousConstraints = combinedConstraints.toSet()
+            // Take the intersection of constraints
+            combinedConstraints.retainAll(newConstraints)
+            // This shouldn't happen if we're diligent about checking that dynamic range
+            // is within the existing constraints before attempting to call
+            // updateConstraints. If it happens, then the dynamic ranges are not mutually
+            // compatible.
+            require(combinedConstraints.isNotEmpty()) {
+                "Constraints of dynamic " +
+                    "range cannot be combined with existing constraints.\n" +
+                    "Dynamic range:\n" +
+                    "  $newDynamicRange\n" +
+                    "Constraints:\n" +
+                    "  $newConstraints\n" +
+                    "Existing constraints:\n" +
+                    "  $previousConstraints"
+            }
+        }
+    }
+
+    private fun findSupportedHdrMatch(
+        rangeToMatch: DynamicRange,
+        fullySpecifiedCandidateRanges: Collection<DynamicRange>,
+        constraints: Set<DynamicRange>
+    ): DynamicRange? {
+        // SDR can never match with HDR
+        if (rangeToMatch.encoding == DynamicRange.ENCODING_SDR) {
+            return null
+        }
+        for (candidateRange in fullySpecifiedCandidateRanges) {
+            val candidateEncoding = candidateRange.encoding
+            check(candidateRange.isFullySpecified) {
+                "Fully specified DynamicRange must have fully defined encoding."
+            }
+            if (candidateEncoding == DynamicRange.ENCODING_SDR) {
+                // Only consider HDR encodings
+                continue
+            }
+            if (canResolveWithinConstraints(
+                    rangeToMatch,
+                    candidateRange,
+                    constraints
+                )
+            ) {
+                return candidateRange
+            }
+        }
+        return null
+    }
+
+    /**
+     * Returns `true` if the dynamic range is ENCODING_UNSPECIFIED and BIT_DEPTH_UNSPECIFIED.
+     */
+    private fun isFullyUnspecified(dynamicRange: DynamicRange): Boolean {
+        return (dynamicRange == DynamicRange.UNSPECIFIED)
+    }
+
+    /**
+     * Returns `true` if the dynamic range has an unspecified HDR encoding, a concrete
+     * encoding with unspecified bit depth, or a concrete bit depth.
+     */
+    private fun isPartiallySpecified(dynamicRange: DynamicRange): Boolean {
+        return dynamicRange.encoding == DynamicRange.ENCODING_HDR_UNSPECIFIED ||
+            (dynamicRange.encoding != DynamicRange.ENCODING_UNSPECIFIED &&
+                dynamicRange.bitDepth == DynamicRange.BIT_DEPTH_UNSPECIFIED) ||
+            (dynamicRange.encoding == DynamicRange.ENCODING_UNSPECIFIED &&
+                dynamicRange.bitDepth != DynamicRange.BIT_DEPTH_UNSPECIFIED)
+    }
+
+    /**
+     * Returns `true` if the test dynamic range can resolve to the candidate, fully specified
+     * dynamic range, taking into account constraints.
+     *
+     *
+     * A range can resolve if test fields are unspecified and appropriately match the fields
+     * of the fully specified dynamic range, or the test fields exactly match the fields of
+     * the fully specified dynamic range.
+     */
+    private fun canResolveWithinConstraints(
+        rangeToResolve: DynamicRange,
+        candidateRange: DynamicRange,
+        constraints: Set<DynamicRange>
+    ): Boolean {
+        if (!constraints.contains(candidateRange)) {
+            Log.debug {
+                "DynamicRangeResolver: Candidate Dynamic range is not within constraints.\n" +
+                    "Dynamic range to resolve:\n" +
+                    "  $rangeToResolve\n" +
+                    "Candidate dynamic range:\n" +
+                    "  $candidateRange"
+            }
+            return false
+        }
+        return canResolveDynamicRange(rangeToResolve, candidateRange)
+    }
+
+    /**
+     * Returns `true` if the test dynamic range can resolve to the fully specified dynamic
+     * range.
+     *
+     *
+     * A range can resolve if test fields are unspecified and appropriately match the fields
+     * of the fully specified dynamic range, or the test fields exactly match the fields of
+     * the fully specified dynamic range.
+     */
+    private fun canResolveDynamicRange(
+        testRange: DynamicRange,
+        fullySpecifiedRange: DynamicRange
+    ): Boolean {
+        check(fullySpecifiedRange.isFullySpecified) {
+            "Fully specified range $fullySpecifiedRange not actually fully specified."
+        }
+        if ((testRange.encoding == DynamicRange.ENCODING_HDR_UNSPECIFIED &&
+                fullySpecifiedRange.encoding == DynamicRange.ENCODING_SDR)
+        ) {
+            return false
+        }
+        return if ((testRange.encoding != DynamicRange.ENCODING_HDR_UNSPECIFIED
+                ) && (testRange.encoding != DynamicRange.ENCODING_UNSPECIFIED
+                ) && (testRange.encoding != fullySpecifiedRange.encoding)
+        ) {
+            false
+        } else (testRange.bitDepth == DynamicRange.BIT_DEPTH_UNSPECIFIED ||
+            testRange.bitDepth == fullySpecifiedRange.bitDepth)
+    }
+
+    @RequiresApi(33)
+    internal object Api33Impl {
+        @DoNotInline
+        fun getRecommended10BitDynamicRange(
+            cameraMetadata: CameraMetadata
+        ): DynamicRange? {
+            val recommendedProfile = cameraMetadata[
+                CameraCharacteristics.REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE]
+            return if (recommendedProfile != null) {
+                DynamicRangeConversions.profileToDynamicRange(recommendedProfile)
+            } else null
+        }
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt
index 4c76b49..6b4e41a 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt
@@ -20,8 +20,11 @@
 import android.graphics.ImageFormat
 import android.graphics.SurfaceTexture
 import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES
+import android.hardware.camera2.CameraCharacteristics.REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE
 import android.hardware.camera2.CameraManager
 import android.hardware.camera2.CameraMetadata
+import android.hardware.camera2.params.DynamicRangeProfiles
 import android.hardware.camera2.params.StreamConfigurationMap
 import android.media.CamcorderProfile.QUALITY_1080P
 import android.media.CamcorderProfile.QUALITY_2160P
@@ -42,6 +45,17 @@
 import androidx.camera.camera2.pipe.integration.adapter.GuaranteedConfigurationsUtil.getLimitedSupportedCombinationList
 import androidx.camera.camera2.pipe.integration.adapter.GuaranteedConfigurationsUtil.getRAWSupportedCombinationList
 import androidx.camera.camera2.pipe.integration.config.CameraAppComponent
+import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_10B_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_8B_SDR_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_8B_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_8B_UNCONSTRAINED_HLG10_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_CONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HDR10_HDR10_PLUS_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HDR10_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HLG10_CONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HLG10_SDR_CONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HLG10_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.LATENCY_NONE
 import androidx.camera.camera2.pipe.testing.FakeCameraBackend
 import androidx.camera.camera2.pipe.testing.FakeCameraDevices
 import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
@@ -49,6 +63,7 @@
 import androidx.camera.core.CameraSelector.LensFacing
 import androidx.camera.core.CameraX
 import androidx.camera.core.CameraXConfig
+import androidx.camera.core.DynamicRange
 import androidx.camera.core.UseCase
 import androidx.camera.core.concurrent.CameraCoordinator
 import androidx.camera.core.impl.AttachedSurfaceInfo
@@ -57,6 +72,8 @@
 import androidx.camera.core.impl.EncoderProfilesProxy
 import androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy
 import androidx.camera.core.impl.ImageFormatConstants
+import androidx.camera.core.impl.ImageInputConfig
+import androidx.camera.core.impl.StreamSpec
 import androidx.camera.core.impl.SurfaceConfig
 import androidx.camera.core.impl.UseCaseConfig
 import androidx.camera.core.impl.UseCaseConfigFactory
@@ -85,6 +102,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 import org.robolectric.RobolectricTestRunner
@@ -197,7 +215,10 @@
         for (combination in combinationList) {
             val isSupported =
                 supportedSurfaceCombination.checkSupported(
-                    CameraMode.DEFAULT, combination.surfaceConfigList
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.DEFAULT,
+                        DynamicRange.BIT_DEPTH_8_BIT
+                    ), combination.surfaceConfigList
                 )
             assertThat(isSupported).isTrue()
         }
@@ -214,7 +235,10 @@
         for (combination in combinationList) {
             val isSupported =
                 supportedSurfaceCombination.checkSupported(
-                    CameraMode.DEFAULT, combination.surfaceConfigList
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.DEFAULT,
+                        DynamicRange.BIT_DEPTH_8_BIT
+                    ), combination.surfaceConfigList
                 )
             assertThat(isSupported).isFalse()
         }
@@ -231,7 +255,10 @@
         for (combination in combinationList) {
             val isSupported =
                 supportedSurfaceCombination.checkSupported(
-                    CameraMode.DEFAULT, combination.surfaceConfigList
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.DEFAULT,
+                        DynamicRange.BIT_DEPTH_8_BIT
+                    ), combination.surfaceConfigList
                 )
             assertThat(isSupported).isFalse()
         }
@@ -248,7 +275,10 @@
         for (combination in combinationList) {
             val isSupported =
                 supportedSurfaceCombination.checkSupported(
-                    CameraMode.DEFAULT, combination.surfaceConfigList
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.DEFAULT,
+                        DynamicRange.BIT_DEPTH_8_BIT
+                    ), combination.surfaceConfigList
                 )
             assertThat(isSupported).isFalse()
         }
@@ -265,7 +295,10 @@
         for (combination in combinationList) {
             val isSupported =
                 supportedSurfaceCombination.checkSupported(
-                    CameraMode.DEFAULT, combination.surfaceConfigList
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.DEFAULT,
+                        DynamicRange.BIT_DEPTH_8_BIT
+                    ), combination.surfaceConfigList
                 )
             assertThat(isSupported).isTrue()
         }
@@ -282,7 +315,10 @@
         for (combination in combinationList) {
             val isSupported =
                 supportedSurfaceCombination.checkSupported(
-                    CameraMode.DEFAULT, combination.surfaceConfigList
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.DEFAULT,
+                        DynamicRange.BIT_DEPTH_8_BIT
+                    ), combination.surfaceConfigList
                 )
             assertThat(isSupported).isFalse()
         }
@@ -299,7 +335,10 @@
         for (combination in combinationList) {
             val isSupported =
                 supportedSurfaceCombination.checkSupported(
-                    CameraMode.DEFAULT, combination.surfaceConfigList
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.DEFAULT,
+                        DynamicRange.BIT_DEPTH_8_BIT
+                    ), combination.surfaceConfigList
                 )
             assertThat(isSupported).isFalse()
         }
@@ -316,7 +355,10 @@
         for (combination in combinationList) {
             val isSupported =
                 supportedSurfaceCombination.checkSupported(
-                    CameraMode.DEFAULT, combination.surfaceConfigList
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.DEFAULT,
+                        DynamicRange.BIT_DEPTH_8_BIT
+                    ), combination.surfaceConfigList
                 )
             assertThat(isSupported).isTrue()
         }
@@ -333,7 +375,10 @@
         for (combination in combinationList) {
             val isSupported =
                 supportedSurfaceCombination.checkSupported(
-                    CameraMode.DEFAULT, combination.surfaceConfigList
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.DEFAULT,
+                        DynamicRange.BIT_DEPTH_8_BIT
+                    ), combination.surfaceConfigList
                 )
             assertThat(isSupported).isFalse()
         }
@@ -353,7 +398,10 @@
         for (combination in combinationList) {
             val isSupported =
                 supportedSurfaceCombination.checkSupported(
-                    CameraMode.DEFAULT, combination.surfaceConfigList
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.DEFAULT,
+                        DynamicRange.BIT_DEPTH_8_BIT
+                    ), combination.surfaceConfigList
                 )
             assertThat(isSupported).isTrue()
         }
@@ -373,7 +421,10 @@
         for (combination in combinationList) {
             val isSupported =
                 supportedSurfaceCombination.checkSupported(
-                    CameraMode.DEFAULT, combination.surfaceConfigList
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.DEFAULT,
+                        DynamicRange.BIT_DEPTH_8_BIT
+                    ), combination.surfaceConfigList
                 )
             assertThat(isSupported).isTrue()
         }
@@ -393,7 +444,10 @@
         for (combination in combinationList) {
             val isSupported =
                 supportedSurfaceCombination.checkSupported(
-                    CameraMode.DEFAULT, combination.surfaceConfigList
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.DEFAULT,
+                        DynamicRange.BIT_DEPTH_8_BIT
+                    ), combination.surfaceConfigList
                 )
             assertThat(isSupported).isTrue()
         }
@@ -413,7 +467,10 @@
         for (combination in combinationList) {
             val isSupported =
                 supportedSurfaceCombination.checkSupported(
-                    CameraMode.DEFAULT, combination.surfaceConfigList
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.DEFAULT,
+                        DynamicRange.BIT_DEPTH_8_BIT
+                    ), combination.surfaceConfigList
                 )
             assertThat(isSupported).isTrue()
         }
@@ -430,7 +487,10 @@
         for (combination in combinationList) {
             val isSupported =
                 supportedSurfaceCombination.checkSupported(
-                    CameraMode.DEFAULT, combination.surfaceConfigList
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.DEFAULT,
+                        DynamicRange.BIT_DEPTH_8_BIT
+                    ), combination.surfaceConfigList
                 )
             assertThat(isSupported).isTrue()
         }
@@ -450,7 +510,10 @@
         for (combination in combinationList) {
             val isSupported =
                 supportedSurfaceCombination.checkSupported(
-                    CameraMode.CONCURRENT_CAMERA, combination.surfaceConfigList
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.CONCURRENT_CAMERA,
+                        DynamicRange.BIT_DEPTH_8_BIT
+                    ), combination.surfaceConfigList
                 )
             assertThat(isSupported).isTrue()
         }
@@ -474,7 +537,10 @@
         GuaranteedConfigurationsUtil.getUltraHighResolutionSupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA, it.surfaceConfigList
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA,
+                        DynamicRange.BIT_DEPTH_8_BIT
+                    ), it.surfaceConfigList
                 )
             ).isTrue()
         }
@@ -1460,9 +1526,19 @@
         attachedSurfaceInfoList: List<AttachedSurfaceInfo> = emptyList(),
         hardwareLevel: Int = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
         capabilities: IntArray? = null,
-        compareWithAtMost: Boolean = false
+        compareWithAtMost: Boolean = false,
+        compareExpectedFps: Range<Int>? = null,
+        cameraMode: Int = CameraMode.DEFAULT,
+        useCasesExpectedDynamicRangeMap: Map<UseCase, DynamicRange> = emptyMap(),
+        dynamicRangeProfiles: DynamicRangeProfiles? = null,
+        default10BitProfile: Long? = null,
     ) {
-        setupCamera(hardwareLevel = hardwareLevel, capabilities = capabilities)
+        setupCamera(
+            hardwareLevel = hardwareLevel,
+            capabilities = capabilities,
+            dynamicRangeProfiles = dynamicRangeProfiles,
+            default10BitProfile = default10BitProfile
+        )
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, fakeCameraMetadata,
             mockEncoderProfilesAdapter
@@ -1472,7 +1548,7 @@
         val useCaseConfigToOutputSizesMap =
             getUseCaseConfigToOutputSizesMap(useCaseConfigMap.values.toList())
         val suggestedStreamSpecs = supportedSurfaceCombination.getSuggestedStreamSpecifications(
-            CameraMode.DEFAULT,
+            cameraMode,
             attachedSurfaceInfoList,
             useCaseConfigToOutputSizesMap
         ).first
@@ -1485,6 +1561,19 @@
             } else {
                 assertThat(sizeIsAtMost(resultSize, expectedSize)).isTrue()
             }
+
+            compareExpectedFps?.let { _ ->
+                assertThat(
+                    suggestedStreamSpecs[useCaseConfigMap[it]]!!.expectedFrameRateRange
+                ).isEqualTo(compareExpectedFps)
+            }
+        }
+
+        useCasesExpectedDynamicRangeMap.keys.forEach {
+            val resultDynamicRange = suggestedStreamSpecs[useCaseConfigMap[it]]!!.dynamicRange
+            val expectedDynamicRange = useCasesExpectedDynamicRangeMap[it]
+
+            assertThat(resultDynamicRange).isEqualTo(expectedDynamicRange)
         }
     }
 
@@ -1519,6 +1608,1165 @@
 
     // //////////////////////////////////////////////////////////////////////////////////////////
     //
+    // StreamSpec selection tests for DynamicRange
+    //
+    // //////////////////////////////////////////////////////////////////////////////////////////
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun check10BitDynamicRangeCombinationsSupported() {
+        setupCamera(
+            capabilities = intArrayOf(
+                CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT
+            )
+        )
+        val supportedSurfaceCombination = SupportedSurfaceCombination(
+            context, fakeCameraMetadata,
+            mockEncoderProfilesAdapter
+        )
+
+        GuaranteedConfigurationsUtil.get10BitSupportedCombinationList().forEach {
+            assertThat(
+                supportedSurfaceCombination.checkSupported(
+                    SupportedSurfaceCombination.FeatureSettings(
+                        CameraMode.DEFAULT,
+                        DynamicRange.BIT_DEPTH_10_BIT
+                    ),
+                    it.surfaceConfigList
+                )
+            ).isTrue()
+        }
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun getSupportedStreamSpecThrows_whenUsingUnsupportedDynamicRange() {
+        val useCase =
+            createUseCase(
+                UseCaseConfigFactory.CaptureType.PREVIEW,
+                dynamicRange = DynamicRange.HDR_UNSPECIFIED_10_BIT
+            )
+        val useCaseExpectedResultMap = mapOf(
+            useCase to Size(0, 0) // Should throw before verifying size
+        )
+
+        Assert.assertThrows(IllegalArgumentException::class.java) {
+            getSuggestedSpecsAndVerify(
+                useCaseExpectedResultMap,
+                capabilities =
+                intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)
+            )
+        }
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun getSupportedStreamSpecThrows_whenUsingConcurrentCameraAndSupported10BitRange() {
+        Shadows.shadowOf(context.packageManager).setSystemFeature(
+            PackageManager.FEATURE_CAMERA_CONCURRENT, true
+        )
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange.HDR_UNSPECIFIED_10_BIT
+        )
+        val useCaseExpectedSizeMap = mapOf(
+            useCase to Size(0, 0) // Should throw before verifying size
+        )
+
+        Assert.assertThrows(IllegalArgumentException::class.java) {
+            getSuggestedSpecsAndVerify(
+                useCaseExpectedSizeMap,
+                cameraMode = CameraMode.CONCURRENT_CAMERA,
+                dynamicRangeProfiles = HLG10_CONSTRAINED
+            )
+        }
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun getSupportedStreamSpecThrows_whenUsingUltraHighResolutionAndSupported10BitRange() {
+        Shadows.shadowOf(context.packageManager).setSystemFeature(
+            PackageManager.FEATURE_CAMERA_CONCURRENT, true
+        )
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange.HDR_UNSPECIFIED_10_BIT
+        )
+        val useCaseExpectedSizeMap = mapOf(
+            useCase to Size(0, 0) // Should throw before verifying size
+        )
+
+        Assert.assertThrows(IllegalArgumentException::class.java) {
+            getSuggestedSpecsAndVerify(
+                useCaseExpectedSizeMap,
+                cameraMode = CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA,
+                dynamicRangeProfiles = HLG10_CONSTRAINED
+            )
+        }
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun dynamicRangeResolver_returnsHlg_dueToMandatory10Bit() {
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange.HDR_UNSPECIFIED_10_BIT
+        )
+        val useCaseExpectedSizeMap = mapOf(
+            useCase to maximumSize
+        )
+        val useCaseExpectedDynamicRangeMap = mapOf(
+            useCase to DynamicRange.HLG_10_BIT
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            dynamicRangeProfiles = HLG10_CONSTRAINED,
+            capabilities =
+            intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+            useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun dynamicRangeResolver_returnsHdr10_dueToRecommended10BitDynamicRange() {
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange.HDR_UNSPECIFIED_10_BIT
+        )
+        val useCaseExpectedSizeMap = mapOf(
+            useCase to maximumSize
+        )
+        val useCaseExpectedDynamicRangeMap = mapOf(
+            useCase to DynamicRange.HDR10_10_BIT
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            dynamicRangeProfiles = HDR10_UNCONSTRAINED,
+            capabilities =
+            intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+            useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap,
+            default10BitProfile = DynamicRangeProfiles.HDR10
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun dynamicRangeResolver_returnsDolbyVision8_dueToSupportedDynamicRanges() {
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange(
+                DynamicRange.ENCODING_HDR_UNSPECIFIED,
+                DynamicRange.BIT_DEPTH_8_BIT
+            )
+        )
+        val useCaseExpectedSizeMap = mapOf(
+            useCase to maximumSize
+        )
+        val useCaseExpectedDynamicRangeMap = mapOf(
+            useCase to DynamicRange.DOLBY_VISION_8_BIT
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            dynamicRangeProfiles = DOLBY_VISION_8B_UNCONSTRAINED,
+            useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun dynamicRangeResolver_returnsDolbyVision8_fromUnspecifiedBitDepth() {
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange(
+                DynamicRange.ENCODING_DOLBY_VISION,
+                DynamicRange.BIT_DEPTH_UNSPECIFIED
+            )
+        )
+        val useCaseExpectedSizeMap = mapOf(
+            useCase to maximumSize
+        )
+        val useCaseExpectedDynamicRangeMap = mapOf(
+            useCase to DynamicRange.DOLBY_VISION_8_BIT
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            dynamicRangeProfiles = DOLBY_VISION_8B_UNCONSTRAINED,
+            useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun dynamicRangeResolver_returnsDolbyVision10_fromUnspecifiedBitDepth() {
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange(
+                DynamicRange.ENCODING_DOLBY_VISION,
+                DynamicRange.BIT_DEPTH_UNSPECIFIED
+            )
+        )
+        val useCaseExpectedSizeMap = mapOf(
+            useCase to maximumSize
+        )
+        val useCaseExpectedDynamicRangeMap = mapOf(
+            useCase to DynamicRange.DOLBY_VISION_10_BIT
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            dynamicRangeProfiles = DOLBY_VISION_10B_UNCONSTRAINED,
+            capabilities =
+            intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+            useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun dynamicRangeResolver_returnsDolbyVision8_fromUnspecifiedHdrWithUnspecifiedBitDepth() {
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange(
+                DynamicRange.ENCODING_HDR_UNSPECIFIED,
+                DynamicRange.BIT_DEPTH_UNSPECIFIED
+            )
+        )
+        val useCaseExpectedSizeMap = mapOf(
+            useCase to maximumSize
+        )
+        val useCaseExpectedDynamicRangeMap = mapOf(
+            useCase to DynamicRange.DOLBY_VISION_8_BIT
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            dynamicRangeProfiles = DOLBY_VISION_8B_UNCONSTRAINED,
+            useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun dynamicRangeResolver_returnsDolbyVision10_fromUnspecifiedHdrWithUnspecifiedBitDepth() {
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange(
+                DynamicRange.ENCODING_HDR_UNSPECIFIED,
+                DynamicRange.BIT_DEPTH_UNSPECIFIED
+            )
+        )
+        val useCaseExpectedSizeMap = mapOf(
+            useCase to maximumSize
+        )
+        val useCaseExpectedDynamicRangeMap = mapOf(
+            useCase to DynamicRange.DOLBY_VISION_10_BIT
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            dynamicRangeProfiles = DOLBY_VISION_CONSTRAINED,
+            capabilities =
+            intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+            useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap,
+            default10BitProfile = DynamicRangeProfiles.DOLBY_VISION_10B_HDR_OEM
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun dynamicRangeResolver_returnsDolbyVision8_withUndefinedBitDepth_andFullyDefinedHlg10() {
+        val videoUseCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE,
+            dynamicRange = DynamicRange.HLG_10_BIT
+        )
+        val previewUseCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange(
+                DynamicRange.ENCODING_DOLBY_VISION,
+                DynamicRange.BIT_DEPTH_UNSPECIFIED
+            )
+        )
+        val useCaseExpectedSizeMap = mutableMapOf(
+            videoUseCase to recordSize,
+            previewUseCase to previewSize
+        )
+        val useCaseExpectedDynamicRangeMap = mapOf(
+            videoUseCase to DynamicRange.HLG_10_BIT,
+            previewUseCase to DynamicRange.DOLBY_VISION_8_BIT
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            dynamicRangeProfiles = DOLBY_VISION_8B_UNCONSTRAINED_HLG10_UNCONSTRAINED,
+            capabilities =
+            intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+            useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun dynamicRangeResolver_returnsDolbyVision10_dueToDynamicRangeConstraints() {
+        // VideoCapture partially defined dynamic range
+        val videoUseCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE,
+            dynamicRange = DynamicRange.HDR_UNSPECIFIED_10_BIT
+        )
+        // Preview fully defined dynamic range
+        val previewUseCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange.DOLBY_VISION_8_BIT,
+
+            )
+        val useCaseExpectedSizeMap = mutableMapOf(
+            videoUseCase to recordSize,
+            previewUseCase to previewSize
+        )
+        val useCaseExpectedDynamicRangeMap = mapOf(
+            videoUseCase to DynamicRange.DOLBY_VISION_10_BIT,
+            previewUseCase to DynamicRange.DOLBY_VISION_8_BIT
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            dynamicRangeProfiles = DOLBY_VISION_CONSTRAINED,
+            capabilities =
+            intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+            useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun dynamicRangeResolver_resolvesUnspecifiedDynamicRange_afterPartiallySpecifiedDynamicRange() {
+        // VideoCapture partially defined dynamic range
+        val videoUseCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE,
+            dynamicRange = DynamicRange.HDR_UNSPECIFIED_10_BIT
+        )
+        // Preview unspecified dynamic range
+        val previewUseCase = createUseCase(UseCaseConfigFactory.CaptureType.PREVIEW)
+
+        val useCaseExpectedSizeMap = mutableMapOf(
+            videoUseCase to recordSize,
+            previewUseCase to previewSize
+        )
+        val useCaseExpectedDynamicRangeMap = mapOf(
+            previewUseCase to DynamicRange.HLG_10_BIT,
+            videoUseCase to DynamicRange.HLG_10_BIT
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            dynamicRangeProfiles = HLG10_UNCONSTRAINED,
+            capabilities =
+            intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+            useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun dynamicRangeResolver_resolvesUnspecifiedDynamicRangeToSdr() {
+        // Preview unspecified dynamic range
+        val useCase = createUseCase(UseCaseConfigFactory.CaptureType.PREVIEW)
+
+        val useCaseExpectedSizeMap = mutableMapOf(
+            useCase to maximumSize
+        )
+        val useCaseExpectedDynamicRangeMap = mapOf(
+            useCase to DynamicRange.SDR
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            dynamicRangeProfiles = HLG10_CONSTRAINED,
+            capabilities =
+            intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+            useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+        )
+    }
+
+    @Test
+    fun dynamicRangeResolver_resolvesToSdr_when10BitNotSupported() {
+        // Preview unspecified dynamic range
+        val useCase = createUseCase(UseCaseConfigFactory.CaptureType.PREVIEW)
+
+        val useCaseExpectedSizeMap = mutableMapOf(
+            useCase to maximumSize
+        )
+        val useCaseExpectedDynamicRangeMap = mapOf(
+            useCase to DynamicRange.SDR
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+        )
+    }
+
+    @Test
+    fun dynamicRangeResolver_resolvesToSdr8Bit_whenSdrWithUnspecifiedBitDepthProvided() {
+        // Preview unspecified dynamic range
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange(
+                DynamicRange.ENCODING_SDR,
+                DynamicRange.BIT_DEPTH_UNSPECIFIED
+            )
+        )
+
+        val useCaseExpectedSizeMap = mutableMapOf(
+            useCase to maximumSize
+        )
+        val useCaseExpectedDynamicRangeMap = mapOf(
+            useCase to DynamicRange.SDR
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun dynamicRangeResolver_resolvesUnspecified8Bit_usingConstraintsFrom10BitDynamicRange() {
+        // VideoCapture has 10-bit HDR range with constraint for 8-bit non-SDR range
+        val videoUseCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE,
+            dynamicRange = DynamicRange.DOLBY_VISION_10_BIT
+        )
+        // Preview unspecified encoding but 8-bit bit depth
+        val previewUseCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange(
+                DynamicRange.ENCODING_UNSPECIFIED,
+                DynamicRange.BIT_DEPTH_8_BIT
+            )
+        )
+
+        val useCaseExpectedSizeMap = mutableMapOf(
+            videoUseCase to recordSize,
+            previewUseCase to previewSize
+        )
+
+        val useCaseExpectedDynamicRangeMap = mapOf(
+            videoUseCase to DynamicRange.DOLBY_VISION_10_BIT,
+            previewUseCase to DynamicRange.DOLBY_VISION_8_BIT
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap,
+            capabilities =
+            intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+            dynamicRangeProfiles = DOLBY_VISION_CONSTRAINED
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun dynamicRangeResolver_resolvesToSdr_forUnspecified8Bit_whenNoOtherDynamicRangesPresent() {
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange(
+                DynamicRange.ENCODING_UNSPECIFIED,
+                DynamicRange.BIT_DEPTH_8_BIT
+            )
+        )
+
+        val useCaseExpectedSizeMap = mutableMapOf(
+            useCase to maximumSize
+        )
+
+        val useCaseExpectedDynamicRangeMap = mapOf(
+            useCase to DynamicRange.SDR
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap,
+            dynamicRangeProfiles = DOLBY_VISION_8B_SDR_UNCONSTRAINED
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun dynamicRangeResolver_resolvesUnspecified8BitToDolbyVision8Bit_whenAlreadyPresent() {
+        // VideoCapture fully resolved Dolby Vision 8-bit
+        val videoUseCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE,
+            dynamicRange = DynamicRange.DOLBY_VISION_8_BIT
+        )
+        // Preview unspecified encoding / 8-bit
+        val previewUseCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange.UNSPECIFIED
+        )
+
+        // Since there are no 10-bit dynamic ranges, the 10-bit resolution table isn't used.
+        // Instead, this will use the camera default LIMITED table which is limited to preview
+        // size for 2 PRIV use cases.
+        val useCaseExpectedSizeMap = mutableMapOf(
+            videoUseCase to previewSize,
+            previewUseCase to previewSize
+        )
+
+        val useCaseExpectedDynamicRangeMap = mapOf(
+            videoUseCase to DynamicRange.DOLBY_VISION_8_BIT,
+            previewUseCase to DynamicRange.DOLBY_VISION_8_BIT
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap,
+            dynamicRangeProfiles = DOLBY_VISION_8B_SDR_UNCONSTRAINED
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun tenBitTable_isUsed_whenAttaching10BitUseCaseToAlreadyAttachedSdrUseCases() {
+        // JPEG use case can't be attached with an existing PRIV + YUV in the 10-bit tables
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE,
+            dynamicRange = DynamicRange.HLG_10_BIT
+        )
+        val useCaseExpectedSizeMap = mapOf(
+            // Size would be valid for LIMITED table
+            useCase to recordSize
+        )
+        // existing surfaces (Preview + ImageAnalysis)
+        val attachedPreview = AttachedSurfaceInfo.create(
+            SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV,
+                SurfaceConfig.ConfigSize.PREVIEW
+            ),
+            ImageFormat.PRIVATE,
+            previewSize,
+            DynamicRange.SDR,
+            listOf(UseCaseConfigFactory.CaptureType.PREVIEW),
+            useCase.currentConfig,
+            /*targetFrameRate=*/null
+        )
+        val attachedAnalysis = AttachedSurfaceInfo.create(
+            SurfaceConfig.create(
+                SurfaceConfig.ConfigType.YUV,
+                SurfaceConfig.ConfigSize.RECORD
+            ),
+            ImageFormat.YUV_420_888,
+            recordSize,
+            DynamicRange.SDR,
+            listOf(UseCaseConfigFactory.CaptureType.IMAGE_ANALYSIS),
+            useCase.currentConfig,
+            /*targetFrameRate=*/null
+        )
+
+        Assert.assertThrows(IllegalArgumentException::class.java) {
+            getSuggestedSpecsAndVerify(
+                useCaseExpectedSizeMap,
+                attachedSurfaceInfoList = listOf(attachedPreview, attachedAnalysis),
+                // LIMITED allows this combination, but 10-bit table does not
+                hardwareLevel = CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+                dynamicRangeProfiles = HLG10_SDR_CONSTRAINED,
+                capabilities =
+                intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)
+            )
+        }
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun dynamicRangeConstraints_causeAutoResolutionToThrow() {
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE,
+            dynamicRange = DynamicRange.HLG_10_BIT
+        )
+        val useCaseExpectedSizeMap = mapOf(
+            // Size would be valid for 10-bit table within constraints
+            useCase to recordSize
+        )
+        // existing surfaces (PRIV + PRIV)
+        val attachedPriv1 = AttachedSurfaceInfo.create(
+            SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV,
+                SurfaceConfig.ConfigSize.PREVIEW
+            ),
+            ImageFormat.PRIVATE,
+            previewSize,
+            DynamicRange.HDR10_10_BIT,
+            listOf(UseCaseConfigFactory.CaptureType.PREVIEW),
+            useCase.currentConfig,
+            /*targetFrameRate=*/null
+        )
+        val attachedPriv2 = AttachedSurfaceInfo.create(
+            SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV,
+                SurfaceConfig.ConfigSize.RECORD
+            ),
+            ImageFormat.YUV_420_888,
+            recordSize,
+            DynamicRange.HDR10_PLUS_10_BIT,
+            listOf(UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE),
+            useCase.currentConfig,
+            /*targetFrameRate=*/null
+        )
+
+        // These constraints say HDR10 and HDR10_PLUS can be combined, but not HLG
+        val constraintsTable =
+            DynamicRangeProfiles(
+                longArrayOf(
+                    DynamicRangeProfiles.HLG10,
+                    DynamicRangeProfiles.HLG10,
+                    LATENCY_NONE,
+                    DynamicRangeProfiles.HDR10,
+                    DynamicRangeProfiles.HDR10 or DynamicRangeProfiles.HDR10_PLUS,
+                    LATENCY_NONE,
+                    DynamicRangeProfiles.HDR10_PLUS,
+                    DynamicRangeProfiles.HDR10_PLUS or DynamicRangeProfiles.HDR10,
+                    LATENCY_NONE
+                )
+            )
+
+        Assert.assertThrows(IllegalArgumentException::class.java) {
+            getSuggestedSpecsAndVerify(
+                useCaseExpectedSizeMap,
+                attachedSurfaceInfoList = listOf(attachedPriv1, attachedPriv2),
+                dynamicRangeProfiles = constraintsTable,
+                capabilities =
+                intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)
+            )
+        }
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canAttachHlgDynamicRange_toExistingSdrStreams() {
+        // JPEG use case can be attached with an existing PRIV + PRIV in the 10-bit tables
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE,
+            dynamicRange = DynamicRange.HLG_10_BIT
+        )
+        val useCaseExpectedSizeMap = mapOf(
+            // Size is valid for 10-bit table within constraints
+            useCase to recordSize
+        )
+        // existing surfaces (PRIV + PRIV)
+        val attachedPriv1 = AttachedSurfaceInfo.create(
+            SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV,
+                SurfaceConfig.ConfigSize.PREVIEW
+            ),
+            ImageFormat.PRIVATE,
+            previewSize,
+            DynamicRange.SDR,
+            listOf(UseCaseConfigFactory.CaptureType.PREVIEW),
+            useCase.currentConfig,
+            /*targetFrameRate=*/null
+        )
+        val attachedPriv2 = AttachedSurfaceInfo.create(
+            SurfaceConfig.create(
+                SurfaceConfig.ConfigType.PRIV,
+                SurfaceConfig.ConfigSize.RECORD
+            ),
+            ImageFormat.YUV_420_888,
+            recordSize,
+            DynamicRange.SDR,
+            listOf(UseCaseConfigFactory.CaptureType.IMAGE_ANALYSIS),
+            useCase.currentConfig,
+            /*targetFrameRate=*/null
+        )
+
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            attachedSurfaceInfoList = listOf(attachedPriv1, attachedPriv2),
+            dynamicRangeProfiles = HLG10_SDR_CONSTRAINED,
+            capabilities =
+            intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun requiredSdrDynamicRangeThrows_whenCombinedWithConstrainedHlg() {
+        // VideoCapture HLG dynamic range
+        val videoUseCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE,
+            dynamicRange = DynamicRange.HLG_10_BIT
+        )
+        // Preview SDR dynamic range
+        val previewUseCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange.SDR
+        )
+
+        val useCaseExpectedSizeMap = mutableMapOf(
+            videoUseCase to recordSize,
+            previewUseCase to previewSize
+        )
+
+        // Fails because HLG10 is constrained to only HLG10
+        Assert.assertThrows(IllegalArgumentException::class.java) {
+            getSuggestedSpecsAndVerify(
+                useCaseExpectedSizeMap,
+                dynamicRangeProfiles = HLG10_CONSTRAINED,
+                capabilities =
+                intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+            )
+        }
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun requiredSdrDynamicRange_canBeCombinedWithUnconstrainedHlg() {
+        // VideoCapture HLG dynamic range
+        val videoUseCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE,
+            dynamicRange = DynamicRange.HLG_10_BIT
+        )
+        // Preview SDR dynamic range
+        val previewUseCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange.SDR
+        )
+
+        val useCaseExpectedSizeMap = mutableMapOf(
+            videoUseCase to recordSize,
+            previewUseCase to previewSize
+        )
+
+        // Should succeed due to HLG10 being unconstrained
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            dynamicRangeProfiles = HLG10_UNCONSTRAINED,
+            capabilities =
+            intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun multiple10BitUnconstrainedDynamicRanges_canBeCombined() {
+        // VideoCapture HDR10 dynamic range
+        val videoUseCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE,
+            dynamicRange = DynamicRange.HDR10_10_BIT
+        )
+        // Preview HDR10_PLUS dynamic range
+        val previewUseCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            dynamicRange = DynamicRange.HDR10_PLUS_10_BIT
+        )
+
+        val useCaseExpectedSizeMap = mutableMapOf(
+            videoUseCase to recordSize,
+            previewUseCase to previewSize
+        )
+
+        // Succeeds because both HDR10 and HDR10_PLUS are unconstrained
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedSizeMap,
+            dynamicRangeProfiles = HDR10_HDR10_PLUS_UNCONSTRAINED,
+            capabilities =
+            intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+        )
+    }
+
+    // //////////////////////////////////////////////////////////////////////////////////////////
+    //
+    // Resolution selection tests for FPS settings
+    //
+    // //////////////////////////////////////////////////////////////////////////////////////////
+
+    @Test
+    fun getSupportedOutputSizes_single_valid_targetFPS() {
+        // a valid target means the device is capable of that fps
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(25, 30)
+        )
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase, Size(3840, 2160))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_single_invalid_targetFPS() {
+        // an invalid target means the device would neve be able to reach that fps
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(65, 70)
+        )
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase, Size(800, 450))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_multiple_targetFPS_first_is_larger() {
+        // a valid target means the device is capable of that fps
+        val useCase1 = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(30, 35)
+        )
+        val useCase2 = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(15, 25)
+        )
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            // both selected size should be no larger than 1920 x 1445
+            put(useCase1, Size(1920, 1445))
+            put(useCase2, Size(1920, 1445))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+            compareWithAtMost = true
+        )
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_multiple_targetFPS_first_is_smaller() {
+        // a valid target means the device is capable of that fps
+        val useCase1 = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(30, 35)
+        )
+        val useCase2 = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(45, 50)
+        )
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            // both selected size should be no larger than 1920 x 1440
+            put(useCase1, Size(1920, 1440))
+            put(useCase2, Size(1920, 1440))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            compareWithAtMost = true
+        )
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_multiple_targetFPS_intersect() {
+        // first and second new use cases have target fps that intersect each other
+        val useCase1 = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(30, 40)
+        )
+        val useCase2 = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(35, 45)
+        )
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            // effective target fps becomes 35-40
+            // both selected size should be no larger than 1920 x 1080
+            put(useCase1, Size(1920, 1080))
+            put(useCase2, Size(1920, 1080))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            compareWithAtMost = true
+        )
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_multiple_cases_first_has_targetFPS() {
+        // first new use case has a target fps, second new use case does not
+        val useCase1 = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(30, 35)
+        )
+        val useCase2 = createUseCase(UseCaseConfigFactory.CaptureType.PREVIEW)
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            // both selected size should be no larger than 1920 x 1440
+            put(useCase1, Size(1920, 1440))
+            put(useCase2, Size(1920, 1440))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            compareWithAtMost = true
+        )
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_multiple_cases_second_has_targetFPS() {
+        // second new use case does not have a target fps, first new use case does not
+        val useCase1 = createUseCase(UseCaseConfigFactory.CaptureType.PREVIEW)
+        val useCase2 = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(30, 35)
+        )
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            // both selected size should be no larger than 1920 x 1440
+            put(useCase1, Size(1920, 1440))
+            put(useCase2, Size(1920, 1440))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            compareWithAtMost = true
+        )
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_attached_with_targetFPS_no_new_targetFPS() {
+        // existing surface with target fps + new use case without a target fps
+        val useCase = createUseCase(UseCaseConfigFactory.CaptureType.PREVIEW)
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            // size should be no larger than 1280 x 960
+            put(useCase, Size(1280, 960))
+        }
+        // existing surface w/ target fps
+        val attachedSurfaceInfo = AttachedSurfaceInfo.create(
+            SurfaceConfig.create(
+                SurfaceConfig.ConfigType.JPEG,
+                SurfaceConfig.ConfigSize.PREVIEW
+            ),
+            ImageFormat.JPEG,
+            Size(1280, 720),
+            DynamicRange.SDR,
+            listOf(UseCaseConfigFactory.CaptureType.PREVIEW),
+            useCase.currentConfig,
+            Range(40, 50)
+        )
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            attachedSurfaceInfoList = listOf(attachedSurfaceInfo),
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            compareWithAtMost = true
+        )
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_attached_with_targetFPS_and_new_targetFPS_no_intersect() {
+        // existing surface with target fps + new use case with target fps that does not intersect
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(30, 35)
+        )
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            // size of new surface should be no larger than 1280 x 960
+            put(useCase, Size(1280, 960))
+        }
+        // existing surface w/ target fps
+        val attachedSurfaceInfo = AttachedSurfaceInfo.create(
+            SurfaceConfig.create(
+                SurfaceConfig.ConfigType.JPEG,
+                SurfaceConfig.ConfigSize.PREVIEW
+            ),
+            ImageFormat.JPEG,
+            Size(1280, 720),
+            DynamicRange.SDR,
+            listOf(UseCaseConfigFactory.CaptureType.PREVIEW),
+            useCase.currentConfig,
+            Range(40, 50)
+        )
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            attachedSurfaceInfoList = listOf(attachedSurfaceInfo),
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            compareWithAtMost = true
+        )
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_attached_with_targetFPS_and_new_targetFPS_with_intersect() {
+        // existing surface with target fps + new use case with target fps that intersect each other
+        val useCase = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(45, 50)
+        )
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            // size of new surface should be no larger than 1280 x 720
+            put(useCase, Size(1280, 720))
+        }
+        // existing surface w/ target fps
+        val attachedSurfaceInfo = AttachedSurfaceInfo.create(
+            SurfaceConfig.create(
+                SurfaceConfig.ConfigType.JPEG,
+                SurfaceConfig.ConfigSize.PREVIEW
+            ),
+            ImageFormat.JPEG,
+            Size(1280, 720),
+            DynamicRange.SDR,
+            listOf(UseCaseConfigFactory.CaptureType.PREVIEW),
+            useCase.currentConfig,
+            Range(40, 50)
+        )
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            attachedSurfaceInfoList = listOf(attachedSurfaceInfo),
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            compareWithAtMost = true
+        )
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_has_device_supported_expectedFrameRateRange() {
+        // use case with target fps
+        val useCase1 = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(15, 25)
+        )
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, Size(4032, 3024))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+            compareWithAtMost = true,
+            compareExpectedFps = Range(10, 22)
+        )
+        // expected fps 10,22 because it has the largest intersection
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_has_exact_device_supported_expectedFrameRateRange() {
+        // use case with target fps
+        val useCase1 = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(30, 40)
+        )
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, Size(1920, 1440))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+            compareWithAtMost = true,
+            compareExpectedFps = Range(30, 30)
+        )
+        // expected fps 30,30 because the fps ceiling is 30
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_has_no_device_supported_expectedFrameRateRange() {
+        // use case with target fps
+        val useCase1 = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(65, 65)
+        )
+
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, Size(800, 450))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+            compareWithAtMost = true,
+            compareExpectedFps = Range(60, 60)
+        )
+        // expected fps 60,60 because it is the closest range available
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_has_multiple_device_supported_expectedFrameRateRange() {
+
+        // use case with target fps
+        val useCase1 = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(36, 45)
+        )
+
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, Size(1280, 960))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+            compareWithAtMost = true,
+            compareExpectedFps = Range(30, 40)
+        )
+        // expected size will give a maximum of 40 fps
+        // expected range 30,40. another range with the same intersection size was 30,50, but 30,40
+        // was selected instead because its range has a larger ratio of intersecting value vs
+        // non-intersecting
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_has_no_device_intersection_expectedFrameRateRange() {
+        // target fps is between ranges, but within device capability (for some reason lol)
+
+        // use case with target fps
+        val useCase1 = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(26, 27)
+        )
+
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, Size(1920, 1440))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+            compareWithAtMost = true,
+            compareExpectedFps = Range(30, 30)
+        )
+        // 30,30 was expected because it is the closest and shortest range to our target fps
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_has_no_device_intersection_equidistant_expectedFrameRateRange() {
+
+        // use case with target fps
+        val useCase1 = createUseCase(
+            UseCaseConfigFactory.CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(26, 26)
+        )
+
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, Size(1920, 1440))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+            compareWithAtMost = true,
+            compareExpectedFps = Range(30, 30)
+        )
+        // 30,30 selected because although there are other ranges that  have the same distance to
+        // the target, 30,30 is the shortest range that also happens to be on the upper side of the
+        // target range
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_has_no_expectedFrameRateRange() {
+        // a valid target means the device is capable of that fps
+
+        // use case with no target fps
+        val useCase1 = createUseCase(UseCaseConfigFactory.CaptureType.PREVIEW)
+
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, Size(4032, 3024))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+            compareExpectedFps = StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED
+        )
+        // since no target fps present, no specific device fps will be selected, and is set to
+        // unspecified: (0,0)
+    }
+
+    // //////////////////////////////////////////////////////////////////////////////////////////
+    //
     // Other tests
     //
     // //////////////////////////////////////////////////////////////////////////////////////////
@@ -1726,6 +2974,8 @@
         highResolutionSupportedSizes: Array<Size>? = null,
         maximumResolutionSupportedSizes: Array<Size>? = null,
         maximumResolutionHighResolutionSupportedSizes: Array<Size>? = null,
+        dynamicRangeProfiles: DynamicRangeProfiles? = null,
+        default10BitProfile: Long? = null,
         capabilities: IntArray? = null,
         cameraId: CameraId = CameraId.fromCamera1Id(0)
     ) {
@@ -1759,6 +3009,17 @@
                 null
             }
 
+        val deviceFPSRanges: Array<Range<Int>?> = arrayOf(
+            Range(10, 22),
+            Range(22, 22),
+            Range(30, 30),
+            Range(30, 50),
+            Range(30, 40),
+            Range(30, 60),
+            Range(50, 60),
+            Range(60, 60)
+        )
+
         val characteristicsMap = mutableMapOf(
             CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL to hardwareLevel,
             CameraCharacteristics.SENSOR_ORIENTATION to sensorOrientation,
@@ -1766,17 +3027,27 @@
             CameraCharacteristics.LENS_FACING to CameraCharacteristics.LENS_FACING_BACK,
             CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES to capabilities,
             CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP to mockMap,
+            CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES to deviceFPSRanges
         ).also { characteristicsMap ->
             mockMaximumResolutionMap?.let {
                 if (Build.VERSION.SDK_INT >= 31) {
                     characteristicsMap[
                         CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
                     ] =
-                    mockMaximumResolutionMap
+                        mockMaximumResolutionMap
                 }
             }
         }
 
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            dynamicRangeProfiles?.let {
+                characteristicsMap[REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES] = it
+            }
+            default10BitProfile?.let {
+                characteristicsMap[REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE] = it
+            }
+        }
+
         // set up FakeCafakeCameraMetadatameraMetadata
         fakeCameraMetadata = FakeCameraMetadata(
             cameraId = cameraId,
@@ -1809,6 +3080,81 @@
                     .thenReturn(it)
             }
         }
+
+        // setup to return different minimum frame durations depending on resolution
+        // minimum frame durations were designated only for the purpose of testing
+        Mockito.`when`(
+            mockMap.getOutputMinFrameDuration(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.eq(Size(4032, 3024))
+            )
+        )
+            .thenReturn(50000000L) // 20 fps, size maximum
+
+        Mockito.`when`(
+            mockMap.getOutputMinFrameDuration(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.eq(Size(3840, 2160))
+            )
+        )
+            .thenReturn(40000000L) // 25, size record
+
+        Mockito.`when`(
+            mockMap.getOutputMinFrameDuration(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.eq(Size(1920, 1440))
+            )
+        )
+            .thenReturn(33333333L) // 30
+
+        Mockito.`when`(
+            mockMap.getOutputMinFrameDuration(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.eq(Size(1920, 1080))
+            )
+        )
+            .thenReturn(28571428L) // 35
+
+        Mockito.`when`(
+            mockMap.getOutputMinFrameDuration(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.eq(Size(1280, 960))
+            )
+        )
+            .thenReturn(25000000L) // 40
+
+        Mockito.`when`(
+            mockMap.getOutputMinFrameDuration(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.eq(Size(1280, 720))
+            )
+        )
+            .thenReturn(22222222L) // 45, size preview/display
+
+        Mockito.`when`(
+            mockMap.getOutputMinFrameDuration(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.eq(Size(960, 544))
+            )
+        )
+            .thenReturn(20000000L) // 50
+
+        Mockito.`when`(
+            mockMap.getOutputMinFrameDuration(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.eq(Size(800, 450))
+            )
+        )
+            .thenReturn(16666666L) // 60fps
+
+        Mockito.`when`(
+            mockMap.getOutputMinFrameDuration(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.eq(Size(640, 480))
+            )
+        )
+            .thenReturn(16666666L) // 60fps
+
         shadowCharacteristics.set(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP, mockMap)
         mockMaximumResolutionMap?.let {
             whenever(mockMaximumResolutionMap.getOutputSizes(ArgumentMatchers.anyInt()))
@@ -1891,7 +3237,8 @@
 
     private fun createUseCase(
         captureType: UseCaseConfigFactory.CaptureType,
-        targetFrameRate: Range<Int>? = null
+        targetFrameRate: Range<Int>? = null,
+        dynamicRange: DynamicRange? = DynamicRange.UNSPECIFIED
     ): UseCase {
         val builder = FakeUseCaseConfig.Builder(
             captureType, when (captureType) {
@@ -1903,6 +3250,10 @@
         targetFrameRate?.let {
             builder.mutableConfig.insertOption(UseCaseConfig.OPTION_TARGET_FRAME_RATE, it)
         }
+        builder.mutableConfig.insertOption(
+            ImageInputConfig.OPTION_INPUT_DYNAMIC_RANGE,
+            dynamicRange
+        )
         return builder.build()
     }
 
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatTest.kt
new file mode 100644
index 0000000..b06ab92
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatTest.kt
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat
+
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.params.DynamicRangeProfiles
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_10B_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_10B_UNCONSTRAINED_SLOW
+import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_8B_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HDR10_PLUS_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HDR10_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HLG10_CONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HLG10_HDR10_CONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HLG10_SDR_CONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HLG10_UNCONSTRAINED
+import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
+import androidx.camera.core.DynamicRange
+import com.google.common.truth.Truth
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadows.ShadowCameraCharacteristics
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class DynamicRangeProfilesCompatTest {
+
+    private val cameraId = CameraId.fromCamera1Id(0)
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canWrapAndUnwrapDynamicRangeProfiles() {
+        val dynamicRangeProfilesCompat =
+            DynamicRangeProfilesCompat.toDynamicRangesCompat(HLG10_UNCONSTRAINED)
+
+        Truth.assertThat(dynamicRangeProfilesCompat).isNotNull()
+        Truth.assertThat(dynamicRangeProfilesCompat?.toDynamicRangeProfiles())
+            .isEqualTo(HLG10_UNCONSTRAINED)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canSupportDynamicRangeFromHlg10Profile() {
+        val dynamicRangeProfilesCompat =
+            DynamicRangeProfilesCompat.toDynamicRangesCompat(HLG10_UNCONSTRAINED)
+        Truth.assertThat(dynamicRangeProfilesCompat?.getSupportedDynamicRanges())
+            .contains(DynamicRange.HLG_10_BIT)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canSupportDynamicRangeFromHdr10Profile() {
+        val dynamicRangeProfilesCompat =
+            DynamicRangeProfilesCompat.toDynamicRangesCompat(HDR10_UNCONSTRAINED)
+        Truth.assertThat(dynamicRangeProfilesCompat?.getSupportedDynamicRanges())
+            .contains(DynamicRange.HDR10_10_BIT)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canSupportDynamicRangeFromHdr10PlusProfile() {
+        val dynamicRangeProfilesCompat =
+            DynamicRangeProfilesCompat.toDynamicRangesCompat(HDR10_PLUS_UNCONSTRAINED)
+        Truth.assertThat(dynamicRangeProfilesCompat?.getSupportedDynamicRanges())
+            .contains(DynamicRange.HDR10_PLUS_10_BIT)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canSupportDynamicRangeFromDolbyVision10bProfile() {
+        val dynamicRangeProfilesCompat =
+            DynamicRangeProfilesCompat.toDynamicRangesCompat(DOLBY_VISION_10B_UNCONSTRAINED)
+        Truth.assertThat(dynamicRangeProfilesCompat?.getSupportedDynamicRanges())
+            .contains(DynamicRange.DOLBY_VISION_10_BIT)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canSupportDynamicRangeFromDolbyVision8bProfile() {
+        val dynamicRangeProfilesCompat =
+            DynamicRangeProfilesCompat.toDynamicRangesCompat(DOLBY_VISION_8B_UNCONSTRAINED)
+        Truth.assertThat(dynamicRangeProfilesCompat?.getSupportedDynamicRanges())
+            .contains(DynamicRange.DOLBY_VISION_8_BIT)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canProduceConcurrentDynamicRangeConstraints() {
+        val hlg10ConstrainedWrapped =
+            DynamicRangeProfilesCompat.toDynamicRangesCompat(HLG10_CONSTRAINED)
+        Truth.assertThat(
+            hlg10ConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(DynamicRange.SDR)
+        ).containsExactly(DynamicRange.SDR)
+        Truth.assertThat(
+            hlg10ConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(
+                DynamicRange.HLG_10_BIT
+            )
+        ).containsExactly(DynamicRange.HLG_10_BIT)
+
+        val hlg10SdrConstrainedWrapped =
+            DynamicRangeProfilesCompat.toDynamicRangesCompat(HLG10_SDR_CONSTRAINED)
+        Truth.assertThat(
+            hlg10SdrConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(DynamicRange.SDR)
+        ).containsExactly(DynamicRange.SDR, DynamicRange.HLG_10_BIT)
+        Truth.assertThat(
+            hlg10SdrConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(
+                DynamicRange.HLG_10_BIT
+            )
+        ).containsExactly(DynamicRange.HLG_10_BIT, DynamicRange.SDR)
+
+        val hlg10Hdr10ConstrainedWrapped =
+            DynamicRangeProfilesCompat.toDynamicRangesCompat(HLG10_HDR10_CONSTRAINED)
+        Truth.assertThat(
+            hlg10Hdr10ConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(DynamicRange.SDR)
+        ).containsExactly(DynamicRange.SDR)
+        Truth.assertThat(
+            hlg10Hdr10ConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(
+                DynamicRange.HLG_10_BIT
+            )
+        ).containsExactly(DynamicRange.HLG_10_BIT, DynamicRange.HDR10_10_BIT)
+        Truth.assertThat(
+            hlg10Hdr10ConstrainedWrapped
+                ?.getDynamicRangeCaptureRequestConstraints(DynamicRange.HDR10_10_BIT)
+        ).containsExactly(DynamicRange.HDR10_10_BIT, DynamicRange.HLG_10_BIT)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun producesDynamicRangeWithCorrectLatency() {
+        val dynamicRangeProfilesCompat =
+            DynamicRangeProfilesCompat.toDynamicRangesCompat(DOLBY_VISION_10B_UNCONSTRAINED_SLOW)
+        Truth.assertThat(dynamicRangeProfilesCompat?.isExtraLatencyPresent(DynamicRange.SDR))
+            .isFalse()
+        Truth.assertThat(
+            dynamicRangeProfilesCompat?.isExtraLatencyPresent(DynamicRange.DOLBY_VISION_10_BIT)
+        ).isTrue()
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canProduceDynamicRangeWithoutConstraints() {
+        val dynamicRangeProfilesCompat =
+            DynamicRangeProfilesCompat.toDynamicRangesCompat(HLG10_UNCONSTRAINED)
+        Truth.assertThat(
+            dynamicRangeProfilesCompat?.getDynamicRangeCaptureRequestConstraints(
+                DynamicRange.HLG_10_BIT
+            )
+        ).isEmpty()
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun producesNullDynamicRangeProfilesFromNullCharacteristics() {
+        val cameraMetadata = FakeCameraMetadata(cameraId = cameraId)
+
+        val dynamicRangeProfilesCompat =
+            DynamicRangeProfilesCompat.fromCameraMetaData(cameraMetadata)
+
+        Truth.assertThat(dynamicRangeProfilesCompat.toDynamicRangeProfiles()).isNull()
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canProduceDynamicRangesCompatFromCharacteristics() {
+        val cameraMetadata = FakeCameraMetadata(
+            cameraId = cameraId, characteristics = mutableMapOf(
+                CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES to HLG10_CONSTRAINED
+            )
+        )
+
+        val dynamicRangeProfilesCompat =
+            DynamicRangeProfilesCompat.fromCameraMetaData(cameraMetadata)
+
+        Truth.assertThat(dynamicRangeProfilesCompat.toDynamicRangeProfiles())
+            .isEqualTo(HLG10_CONSTRAINED)
+    }
+
+    @Test
+    fun alwaysSupportsOnlySdrWithoutDynamicRangeProfilesInCharacteristics() {
+        val cameraMetadata = FakeCameraMetadata(cameraId = cameraId)
+
+        val dynamicRangeProfilesCompat =
+            DynamicRangeProfilesCompat.fromCameraMetaData(cameraMetadata)
+
+        Truth.assertThat(dynamicRangeProfilesCompat.getSupportedDynamicRanges())
+            .containsExactly(DynamicRange.SDR)
+        Truth.assertThat(
+            dynamicRangeProfilesCompat.getDynamicRangeCaptureRequestConstraints(DynamicRange.SDR)
+        ).containsExactly(DynamicRange.SDR)
+    }
+
+    @Test
+    fun unsupportedDynamicRangeAlwaysThrowsException() {
+        val characteristics = mutableMapOf<CameraCharacteristics.Key<*>, Any?>()
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            characteristics[CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES] =
+                DOLBY_VISION_8B_UNCONSTRAINED
+        }
+        val cameraMetadata = FakeCameraMetadata(
+            cameraId = cameraId, characteristics = characteristics
+        )
+
+        val dynamicRangeProfilesCompat =
+            DynamicRangeProfilesCompat.fromCameraMetaData(cameraMetadata)
+
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+            Truth.assertThat(dynamicRangeProfilesCompat.getSupportedDynamicRanges())
+                .containsExactly(DynamicRange.SDR)
+        } else {
+            Truth.assertThat(dynamicRangeProfilesCompat.getSupportedDynamicRanges())
+                .containsExactly(
+                    DynamicRange.SDR, DynamicRange.DOLBY_VISION_8_BIT
+                )
+        }
+
+        Assert.assertThrows(IllegalArgumentException::class.java) {
+            dynamicRangeProfilesCompat
+                .getDynamicRangeCaptureRequestConstraints(DynamicRange.DOLBY_VISION_10_BIT)
+        }
+
+        Assert.assertThrows(IllegalArgumentException::class.java) {
+            dynamicRangeProfilesCompat.isExtraLatencyPresent(DynamicRange.DOLBY_VISION_10_BIT)
+        }
+    }
+
+    @Test
+    fun sdrHasNoExtraLatency() {
+        val characteristics = mutableMapOf<CameraCharacteristics.Key<*>, Any?>()
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            characteristics[CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES] =
+                HLG10_CONSTRAINED
+        }
+        val cameraMetadata = FakeCameraMetadata(
+            cameraId = cameraId, characteristics = characteristics
+        )
+        val dynamicRangeProfilesCompat =
+            DynamicRangeProfilesCompat.fromCameraMetaData(cameraMetadata)
+
+        Truth.assertThat(dynamicRangeProfilesCompat.isExtraLatencyPresent(DynamicRange.SDR))
+            .isFalse()
+    }
+
+    @Test
+    fun sdrHasSdrConstraint_whenConcurrentDynamicRangesNotSupported() {
+        val characteristics = mutableMapOf<CameraCharacteristics.Key<*>, Any?>()
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            characteristics[CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES] =
+                HLG10_CONSTRAINED
+        }
+        val cameraMetadata = FakeCameraMetadata(
+            cameraId = cameraId, characteristics = characteristics
+        )
+        val dynamicRangeProfilesCompat =
+            DynamicRangeProfilesCompat.fromCameraMetaData(cameraMetadata)
+
+        Truth.assertThat(
+            dynamicRangeProfilesCompat.getDynamicRangeCaptureRequestConstraints(DynamicRange.SDR)
+        )
+            .containsExactly(DynamicRange.SDR)
+    }
+}
+
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+fun ShadowCameraCharacteristics.addDynamicRangeProfiles(
+    dynamicRangeProfiles: DynamicRangeProfiles
+) {
+    set(
+        CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES,
+        dynamicRangeProfiles
+    )
+}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/FlashTooSlowQuirkTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/FlashTooSlowQuirkTest.kt
index fd8fa84..d8f0400 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/FlashTooSlowQuirkTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/FlashTooSlowQuirkTest.kt
@@ -58,6 +58,9 @@
             arrayOf("sm-a320f", CameraCharacteristics.LENS_FACING_BACK, true),
             arrayOf("SM-A320FL", CameraCharacteristics.LENS_FACING_BACK, true),
             arrayOf("Samsung S7", CameraCharacteristics.LENS_FACING_BACK, false),
+            arrayOf("moto g(20)", CameraCharacteristics.LENS_FACING_BACK, true),
+            arrayOf("itel l6006", CameraCharacteristics.LENS_FACING_BACK, true),
+            arrayOf("rmx3231", CameraCharacteristics.LENS_FACING_BACK, true),
         )
     }
 
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeTestCases.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeTestCases.kt
new file mode 100644
index 0000000..bcaae5a
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeTestCases.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:RequiresApi(33)
+
+package androidx.camera.camera2.pipe.integration.internal
+
+import android.hardware.camera2.params.DynamicRangeProfiles
+import android.hardware.camera2.params.DynamicRangeProfiles.DOLBY_VISION_10B_HDR_OEM
+import android.hardware.camera2.params.DynamicRangeProfiles.DOLBY_VISION_8B_HDR_OEM
+import android.hardware.camera2.params.DynamicRangeProfiles.HDR10
+import android.hardware.camera2.params.DynamicRangeProfiles.HDR10_PLUS
+import android.hardware.camera2.params.DynamicRangeProfiles.HLG10
+import android.hardware.camera2.params.DynamicRangeProfiles.STANDARD
+import androidx.annotation.RequiresApi
+
+val HLG10_UNCONSTRAINED by lazy {
+    DynamicRangeProfiles(longArrayOf(HLG10, 0, 0))
+}
+
+val HLG10_CONSTRAINED by lazy {
+    DynamicRangeProfiles(
+        longArrayOf(
+            HLG10, HLG10, LATENCY_NONE
+        )
+    )
+}
+
+val HLG10_SDR_CONSTRAINED by lazy {
+    DynamicRangeProfiles(
+        longArrayOf(
+            HLG10, HLG10 or STANDARD, LATENCY_NONE
+        )
+    )
+}
+
+val HLG10_HDR10_CONSTRAINED by lazy {
+    DynamicRangeProfiles(
+        longArrayOf(
+            HLG10, HLG10 or HDR10, LATENCY_NONE,
+            HDR10, HDR10 or HLG10, LATENCY_NONE
+        )
+    )
+}
+
+val HDR10_UNCONSTRAINED by lazy {
+    DynamicRangeProfiles(
+        longArrayOf(
+            HLG10, CONSTRAINTS_NONE, LATENCY_NONE, // HLG is mandated
+            HDR10, CONSTRAINTS_NONE, LATENCY_NONE
+        )
+    )
+}
+
+val HDR10_PLUS_UNCONSTRAINED by lazy {
+    DynamicRangeProfiles(
+        longArrayOf(
+            HLG10, CONSTRAINTS_NONE, LATENCY_NONE, // HLG is mandated
+            HDR10_PLUS, CONSTRAINTS_NONE, LATENCY_NONE
+        )
+    )
+}
+
+val HDR10_HDR10_PLUS_UNCONSTRAINED by lazy {
+    DynamicRangeProfiles(
+        longArrayOf(
+            HLG10, CONSTRAINTS_NONE, LATENCY_NONE, // HLG is mandated
+            HDR10, CONSTRAINTS_NONE, LATENCY_NONE,
+            HDR10_PLUS, CONSTRAINTS_NONE, LATENCY_NONE
+        )
+    )
+}
+
+val DOLBY_VISION_10B_UNCONSTRAINED by lazy {
+    DynamicRangeProfiles(
+        longArrayOf(
+            HLG10, CONSTRAINTS_NONE, LATENCY_NONE, // HLG is mandated
+            DOLBY_VISION_10B_HDR_OEM, CONSTRAINTS_NONE, LATENCY_NONE
+        )
+    )
+}
+
+val DOLBY_VISION_10B_UNCONSTRAINED_SLOW by lazy {
+    DynamicRangeProfiles(
+        longArrayOf(
+            HLG10, CONSTRAINTS_NONE, LATENCY_NONE, // HLG is mandated
+            DOLBY_VISION_10B_HDR_OEM, CONSTRAINTS_NONE, LATENCY_NON_ZERO
+        )
+    )
+}
+
+val DOLBY_VISION_8B_UNCONSTRAINED by lazy {
+    DynamicRangeProfiles(
+        longArrayOf(
+            DOLBY_VISION_8B_HDR_OEM, CONSTRAINTS_NONE, LATENCY_NONE
+        )
+    )
+}
+
+val DOLBY_VISION_8B_SDR_UNCONSTRAINED by lazy {
+    DynamicRangeProfiles(
+        longArrayOf(
+            DOLBY_VISION_8B_HDR_OEM, DOLBY_VISION_8B_HDR_OEM or STANDARD, LATENCY_NONE
+        )
+    )
+}
+
+val DOLBY_VISION_8B_UNCONSTRAINED_HLG10_UNCONSTRAINED by lazy {
+    DynamicRangeProfiles(
+        longArrayOf(
+            HLG10, CONSTRAINTS_NONE, LATENCY_NONE,
+            DOLBY_VISION_8B_HDR_OEM, CONSTRAINTS_NONE, LATENCY_NONE,
+        )
+    )
+}
+
+val DOLBY_VISION_CONSTRAINED by lazy {
+    DynamicRangeProfiles(
+        longArrayOf(
+            HLG10, HLG10, LATENCY_NONE, // HLG is mandated
+            DOLBY_VISION_10B_HDR_OEM, DOLBY_VISION_10B_HDR_OEM or DOLBY_VISION_8B_HDR_OEM,
+            LATENCY_NONE,
+            DOLBY_VISION_8B_HDR_OEM, DOLBY_VISION_8B_HDR_OEM or DOLBY_VISION_10B_HDR_OEM,
+            LATENCY_NONE
+        )
+    )
+}
+
+const val LATENCY_NONE = 0L
+private const val LATENCY_NON_ZERO = 3L
+private const val CONSTRAINTS_NONE = 0L
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfoTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfoTest.kt
index 401c134..420f747 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfoTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfoTest.kt
@@ -178,6 +178,14 @@
             override fun getSupportedDynamicRanges(): MutableSet<DynamicRange> {
                 throw NotImplementedError("Not used in testing")
             }
+
+            override fun isPreviewStabilizationSupported(): Boolean {
+                throw NotImplementedError("Not used in testing")
+            }
+
+            override fun isVideoStabilizationSupported(): Boolean {
+                throw NotImplementedError("Not used in testing")
+            }
         }
         Camera2CameraInfo.from(wrongCameraInfo)
     }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraMetadata.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraMetadata.kt
index ec0be0e..7274b3b 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraMetadata.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2CameraMetadata.kt
@@ -36,8 +36,7 @@
  * This allows all fields to be accessed and return reasonable values on all OS versions.
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-internal class Camera2CameraMetadata
-constructor(
+internal class Camera2CameraMetadata(
     override val camera: CameraId,
     override val isRedacted: Boolean,
     private val characteristics: CameraCharacteristics,
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
index e7187fe..ba3953d7 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
@@ -17,7 +17,9 @@
 package androidx.camera.camera2.pipe.compat
 
 import android.content.Context
+import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CameraManager
+import android.os.Build
 import android.util.ArrayMap
 import androidx.annotation.GuardedBy
 import androidx.annotation.RequiresApi
@@ -105,7 +107,13 @@
 
                 // Merge the camera specific and global cache blocklists together.
                 // this will prevent these values from being cached after first access.
-                val cameraBlocklist = cameraMetadataConfig.cameraCacheBlocklist[cameraId]
+                val cameraBlocklist =
+                    if (shouldBlockSensorOrientationCache(characteristics)) {
+                        (cameraMetadataConfig.cameraCacheBlocklist[cameraId] ?: emptySet()) +
+                            CameraCharacteristics.SENSOR_ORIENTATION
+                    } else {
+                        cameraMetadataConfig.cameraCacheBlocklist[cameraId]
+                    }
                 val cacheBlocklist =
                     if (cameraBlocklist == null) {
                         cameraMetadataConfig.cacheBlocklist
@@ -146,4 +154,9 @@
     }
 
     private fun isMetadataRedacted(): Boolean = !permissions.hasCameraPermission
+
+    private fun shouldBlockSensorOrientationCache(characteristics: CameraCharacteristics): Boolean {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2 &&
+            characteristics[CameraCharacteristics.INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP] != null
+    }
 }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
index 78ff31d..5422996 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
@@ -1122,7 +1122,7 @@
 
         try {
             mSupportedSurfaceCombination.getSuggestedStreamSpecifications(cameraMode,
-                    attachedSurfaces, useCaseConfigToSizeMap);
+                    attachedSurfaces, useCaseConfigToSizeMap, false);
         } catch (IllegalArgumentException e) {
             debugLog("Surface combination with metering repeating  not supported!", e);
             return false;
@@ -1480,7 +1480,11 @@
                 }
 
                 Logger.e(TAG, "Unable to configure camera " + Camera2CameraImpl.this, t);
-                resetCaptureSession(/*abortInFlightCaptures=*/false);
+
+                // Reset capture session if the latest capture session fails to open.
+                if (mCaptureSession == captureSession) {
+                    resetCaptureSession(/*abortInFlightCaptures=*/false);
+                }
             }
         }, mExecutor);
     }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
index 3729cf8..edcf484 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
@@ -16,10 +16,12 @@
 
 package androidx.camera.camera2.internal;
 
+import static android.hardware.camera2.CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES;
+import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON;
+import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION;
 import static android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING;
 import static android.hardware.camera2.CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME;
 import static android.hardware.camera2.CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN;
-
 import static androidx.camera.camera2.internal.ZslUtil.isCapabilitySupported;
 
 import android.hardware.camera2.CameraCharacteristics;
@@ -53,6 +55,7 @@
 import androidx.camera.core.ExposureState;
 import androidx.camera.core.FocusMeteringAction;
 import androidx.camera.core.Logger;
+import androidx.camera.core.PreviewCapabilities;
 import androidx.camera.core.ZoomState;
 import androidx.camera.core.impl.CameraCaptureCallback;
 import androidx.camera.core.impl.CameraInfoInternal;
@@ -505,6 +508,42 @@
         }
     }
 
+    @NonNull
+    @Override
+    public PreviewCapabilities getPreviewCapabilities() {
+        return Camera2PreviewCapabilities.from(this);
+    }
+
+    @Override
+    public boolean isVideoStabilizationSupported() {
+        int[] availableVideoStabilizationModes =
+                mCameraCharacteristicsCompat.get(
+                        CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES);
+        if (availableVideoStabilizationModes != null) {
+            for (int mode : availableVideoStabilizationModes) {
+                if (mode == CONTROL_VIDEO_STABILIZATION_MODE_ON) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isPreviewStabilizationSupported() {
+        int[] availableVideoStabilizationModes =
+                mCameraCharacteristicsCompat.get(
+                        CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES);
+        if (availableVideoStabilizationModes != null) {
+            for (int mode : availableVideoStabilizationModes) {
+                if (mode == CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * Gets the implementation of {@link Camera2CameraInfo}.
      */
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java
index 60c74f0..2d66b8c 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CaptureRequestBuilder.java
@@ -28,6 +28,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
 import androidx.camera.camera2.impl.Camera2ImplConfig;
 import androidx.camera.camera2.interop.CaptureRequestOptions;
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
@@ -37,6 +38,7 @@
 import androidx.camera.core.impl.Config;
 import androidx.camera.core.impl.DeferrableSurface;
 import androidx.camera.core.impl.StreamSpec;
+import androidx.camera.core.impl.stabilization.StabilizationMode;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -111,6 +113,21 @@
 
     }
 
+    @VisibleForTesting
+    static void applyVideoStabilization(@NonNull CaptureConfig captureConfig,
+            @NonNull CaptureRequest.Builder builder) {
+        if (captureConfig.getPreviewStabilizationMode() == StabilizationMode.OFF
+                || captureConfig.getVideoStabilizationMode() == StabilizationMode.OFF) {
+            builder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
+                    CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF);
+        } else if (captureConfig.getPreviewStabilizationMode() == StabilizationMode.ON) {
+            builder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
+                    CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION);
+        } else if (captureConfig.getVideoStabilizationMode() == StabilizationMode.ON) {
+            builder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
+                    CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON);
+        }
+    }
 
     /**
      * Builds a {@link CaptureRequest} from a {@link CaptureConfig} and a {@link CameraDevice}.
@@ -155,6 +172,8 @@
 
         applyAeFpsRange(captureConfig, builder);
 
+        applyVideoStabilization(captureConfig, builder);
+
         if (captureConfig.getImplementationOptions().containsOption(
                 CaptureConfig.OPTION_ROTATION)) {
             builder.set(CaptureRequest.JPEG_ORIENTATION,
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManager.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManager.java
index 7c1d95e..1322af7 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManager.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManager.java
@@ -171,7 +171,8 @@
             @CameraMode.Mode int cameraMode,
             @NonNull String cameraId,
             @NonNull List<AttachedSurfaceInfo> existingSurfaces,
-            @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap) {
+            @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap,
+            boolean isPreviewStabilizationOn) {
         Preconditions.checkArgument(!newUseCaseConfigsSupportedSizeMap.isEmpty(),
                 "No new use cases to be bound.");
 
@@ -186,6 +187,7 @@
         return supportedSurfaceCombination.getSuggestedStreamSpecifications(
                 cameraMode,
                 existingSurfaces,
-                newUseCaseConfigsSupportedSizeMap);
+                newUseCaseConfigsSupportedSizeMap,
+                isPreviewStabilizationOn);
     }
 }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2PreviewCapabilities.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2PreviewCapabilities.java
new file mode 100644
index 0000000..aa25edd
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2PreviewCapabilities.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.CameraInfo;
+import androidx.camera.core.PreviewCapabilities;
+import androidx.camera.core.impl.CameraInfoInternal;
+
+/**
+ * Camera2 implementation of {@link PreviewCapabilities}.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class Camera2PreviewCapabilities implements PreviewCapabilities {
+
+    private boolean mIsStabilizationSupported = false;
+
+    Camera2PreviewCapabilities(@NonNull CameraInfoInternal cameraInfoInternal) {
+        mIsStabilizationSupported = cameraInfoInternal.isPreviewStabilizationSupported();
+    }
+
+
+    @NonNull
+    static Camera2PreviewCapabilities from(@NonNull CameraInfo cameraInfo) {
+        return new Camera2PreviewCapabilities((CameraInfoInternal) cameraInfo);
+    }
+
+
+    @Override
+    public boolean isStabilizationSupported() {
+        return mIsStabilizationSupported;
+    }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2SessionOptionUnpacker.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2SessionOptionUnpacker.java
index e64c90c..dc1207c 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2SessionOptionUnpacker.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2SessionOptionUnpacker.java
@@ -89,6 +89,10 @@
                         camera2Config.getSessionCaptureCallback(
                                 Camera2CaptureCallbacks.createNoOpCallback())));
 
+        // Set video stabilization mode
+        builder.setVideoStabilization(config.getVideoStabilizationMode());
+        builder.setPreviewStabilization(config.getPreviewStabilizationMode());
+
         // Copy extended Camera2 configurations
         MutableOptionsBundle extendedConfig = MutableOptionsBundle.create();
         extendedConfig.insertOption(Camera2ImplConfig.SESSION_PHYSICAL_CAMERA_ID_OPTION,
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java
index d18bd1b..5a4413f 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java
@@ -833,6 +833,93 @@
     }
 
     /**
+     * Returns the supported stream combinations for preview stabilization.
+     */
+    @RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
+    @NonNull
+    public static List<SurfaceCombination> getPreviewStabilizationSupportedCombinationList() {
+        List<SurfaceCombination> combinationList = new ArrayList<>();
+
+        // (PRIV, s1440p)
+        SurfaceCombination surfaceCombination1 = new SurfaceCombination();
+        surfaceCombination1.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.s1440p));
+        combinationList.add(surfaceCombination1);
+
+        // (YUV, s1440p)
+        SurfaceCombination surfaceCombination2 = new SurfaceCombination();
+        surfaceCombination2.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.s1440p));
+        combinationList.add(surfaceCombination2);
+
+        // (PRIV, s1440p) + (JPEG, MAXIMUM)
+        SurfaceCombination surfaceCombination3 = new SurfaceCombination();
+        surfaceCombination3.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.s1440p));
+        surfaceCombination3.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination3);
+
+        // (YUV, s1440p) + (JPEG, MAXIMUM)
+        SurfaceCombination surfaceCombination4 = new SurfaceCombination();
+        surfaceCombination4.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.s1440p));
+        surfaceCombination4.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination4);
+
+        // (PRIV, s1440p) + (YUV, MAXIMUM)
+        SurfaceCombination surfaceCombination5 = new SurfaceCombination();
+        surfaceCombination5.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.s1440p));
+        surfaceCombination5.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination5);
+
+        // (YUV, s1440p) + (YUV, MAXIMUM)
+        SurfaceCombination surfaceCombination6 = new SurfaceCombination();
+        surfaceCombination6.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.s1440p));
+        surfaceCombination6.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination6);
+
+        // (PRIV, PREVIEW) + (PRIV, s1440)
+        SurfaceCombination surfaceCombination7 = new SurfaceCombination();
+        surfaceCombination7.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination7.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.s1440p));
+        combinationList.add(surfaceCombination7);
+
+        // (YUV, PREVIEW) + (PRIV, s1440)
+        SurfaceCombination surfaceCombination8 = new SurfaceCombination();
+        surfaceCombination8.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        surfaceCombination8.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.s1440p));
+        combinationList.add(surfaceCombination8);
+
+        // (PRIV, PREVIEW) + (YUV, s1440)
+        SurfaceCombination surfaceCombination9 = new SurfaceCombination();
+        surfaceCombination9.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination9.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.s1440p));
+        combinationList.add(surfaceCombination9);
+
+        // (YUV, PREVIEW) + (YUV, s1440)
+        SurfaceCombination surfaceCombination10 = new SurfaceCombination();
+        surfaceCombination10.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        surfaceCombination10.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.s1440p));
+        combinationList.add(surfaceCombination10);
+
+        return combinationList;
+    }
+
+    /**
      * Returns the supported stream combinations based on the hardware level and capabilities of
      * the device.
      */
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
index 01357dc..e89c415 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
@@ -452,7 +452,12 @@
         switch (mProcessorState) {
             case ON_CAPTURE_SESSION_ENDED:
             case SESSION_INITIALIZED:
-                future.addListener(() -> mSessionProcessor.deInitSession(), mExecutor);
+                future.addListener(() -> {
+                    Logger.d(TAG, "== deInitSession (id=" + mInstanceId + ")");
+                    mSessionProcessor.deInitSession();
+                    // Use direct executor to ensure deInitSession is invoked as soon as session is
+                    // closed.
+                }, CameraXExecutors.directExecutor());
                 break;
             default:
                 break;
@@ -473,11 +478,12 @@
     }
 
     void onConfigured(@NonNull CaptureSession captureSession) {
-        Preconditions.checkArgument(mProcessorState == ProcessorState.SESSION_INITIALIZED,
-                "Invalid state state:" + mProcessorState);
-
+        if (mProcessorState != ProcessorState.SESSION_INITIALIZED) {
+            return;
+        }
         mRequestProcessor = new Camera2RequestProcessor(captureSession,
                 getSessionProcessorSurfaceList(mProcessorSessionConfig.getSurfaces()));
+        Logger.d(TAG, "== onCaptureSessinStarted (id = " + mInstanceId + ")");
         mSessionProcessor.onCaptureSessionStart(mRequestProcessor);
         mProcessorState = ProcessorState.ON_CAPTURE_SESSION_STARTED;
 
@@ -534,6 +540,7 @@
         Logger.d(TAG, "close (id=" + mInstanceId + ") state=" + mProcessorState);
 
         if (mProcessorState == ProcessorState.ON_CAPTURE_SESSION_STARTED) {
+            Logger.d(TAG, "== onCaptureSessionEnd (id = " + mInstanceId + ")");
             mSessionProcessor.onCaptureSessionEnd();
             if (mRequestProcessor != null) {
                 mRequestProcessor.close();
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
index cd453ff..a6d9b50 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
@@ -18,7 +18,6 @@
 
 import static android.content.pm.PackageManager.FEATURE_CAMERA_CONCURRENT;
 import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES;
-
 import static androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_1080P;
 import static androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_480P;
 import static androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_VGA;
@@ -95,6 +94,8 @@
     private final List<SurfaceCombination> mSurfaceCombinations = new ArrayList<>();
     private final List<SurfaceCombination> mUltraHighSurfaceCombinations = new ArrayList<>();
     private final List<SurfaceCombination> mConcurrentSurfaceCombinations = new ArrayList<>();
+    private final List<SurfaceCombination> mPreviewStabilizationSurfaceCombinations =
+            new ArrayList<>();
     private final Map<FeatureSettings, List<SurfaceCombination>>
             mFeatureSettingsToSupportedCombinationsMap = new HashMap<>();
     private final List<SurfaceCombination> mSurfaceCombinations10Bit = new ArrayList<>();
@@ -110,6 +111,7 @@
     private boolean mIsConcurrentCameraModeSupported = false;
     private boolean mIsStreamUseCaseSupported = false;
     private boolean mIsUltraHighResolutionSensorSupported = false;
+    private boolean mIsPreviewStabilizationSupported = false;
     @VisibleForTesting
     SurfaceSizeDefinition mSurfaceSizeDefinition;
     List<Integer> mSurfaceSizeDefinitionFormats = new ArrayList<>();
@@ -184,6 +186,12 @@
             generateStreamUseCaseSupportedCombinationList();
         }
 
+        mIsPreviewStabilizationSupported =
+                VideoStabilizationUtil.isPreviewStabilizationSupported(mCharacteristics);
+        if (mIsPreviewStabilizationSupported) {
+            generatePreviewStabilizationSupportedCombinationList();
+        }
+
         generateSurfaceSizeDefinition();
         checkCustomization();
     }
@@ -267,7 +275,8 @@
                     supportedSurfaceCombinations.addAll(mSurfaceCombinations);
                     break;
                 default:
-                    supportedSurfaceCombinations.addAll(mSurfaceCombinations);
+                    supportedSurfaceCombinations.addAll(featureSettings.isPreviewStabilizationOn()
+                            ? mPreviewStabilizationSurfaceCombinations : mSurfaceCombinations);
                     break;
             }
         } else if (featureSettings.getRequiredMaxBitDepth() == DynamicRange.BIT_DEPTH_10_BIT) {
@@ -521,6 +530,7 @@
      * @param attachedSurfaces                  the existing surfaces.
      * @param newUseCaseConfigsSupportedSizeMap newly added UseCaseConfig to supported output
      *                                          sizes map.
+     * @param isPreviewStabilizationOn          whether the preview stabilization is enabled.
      * @return the suggested stream specifications, which is a pair of mappings. The first
      * mapping is from UseCaseConfig to the suggested stream specification representing new
      * UseCases. The second mapping is from attachedSurfaceInfo to the suggested stream
@@ -536,7 +546,8 @@
             getSuggestedStreamSpecifications(
             @CameraMode.Mode int cameraMode,
             @NonNull List<AttachedSurfaceInfo> attachedSurfaces,
-            @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap) {
+            @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap,
+            boolean isPreviewStabilizationOn) {
         // Refresh Preview Size based on current display configurations.
         refreshPreviewSize();
         List<SurfaceConfig> surfaceConfigs = new ArrayList<>();
@@ -553,7 +564,8 @@
                 mDynamicRangeResolver.resolveAndValidateDynamicRanges(attachedSurfaces,
                         newUseCaseConfigs, useCasesPriorityOrder);
         int requiredMaxBitDepth = getRequiredMaxBitDepth(resolvedDynamicRanges);
-        FeatureSettings featureSettings = FeatureSettings.of(cameraMode, requiredMaxBitDepth);
+        FeatureSettings featureSettings = FeatureSettings.of(cameraMode, requiredMaxBitDepth,
+                isPreviewStabilizationOn);
         if (cameraMode != CameraMode.DEFAULT
                 && requiredMaxBitDepth == DynamicRange.BIT_DEPTH_10_BIT) {
             throw new IllegalArgumentException(String.format("No supported surface combination is "
@@ -1104,6 +1116,13 @@
         }
     }
 
+    private void generatePreviewStabilizationSupportedCombinationList() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            mPreviewStabilizationSurfaceCombinations.addAll(
+                    GuaranteedConfigurationsUtil.getPreviewStabilizationSupportedCombinationList());
+        }
+    }
+
     private void checkCustomization() {
         // TODO(b/119466260): Integrate found feasible stream combinations into supported list
     }
@@ -1334,9 +1353,10 @@
     abstract static class FeatureSettings {
         @NonNull
         static FeatureSettings of(@CameraMode.Mode int cameraMode,
-                @RequiredMaxBitDepth int requiredMaxBitDepth) {
+                @RequiredMaxBitDepth int requiredMaxBitDepth,
+                boolean isPreviewStabilizationOn) {
             return new AutoValue_SupportedSurfaceCombination_FeatureSettings(
-                    cameraMode, requiredMaxBitDepth);
+                    cameraMode, requiredMaxBitDepth, isPreviewStabilizationOn);
         }
 
         /**
@@ -1364,5 +1384,10 @@
          * {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT}.
          */
         abstract @RequiredMaxBitDepth int getRequiredMaxBitDepth();
+
+        /**
+         * Whether the preview stabilization is enabled.
+         */
+        abstract boolean isPreviewStabilizationOn();
     }
 }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/TemplateTypeUtil.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/TemplateTypeUtil.java
index 905e01f..31f8b81 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/TemplateTypeUtil.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/TemplateTypeUtil.java
@@ -49,8 +49,12 @@
                         ? CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG :
                         CameraDevice.TEMPLATE_PREVIEW;
             case VIDEO_CAPTURE:
-            case STREAM_SHARING:
                 return CameraDevice.TEMPLATE_RECORD;
+            case STREAM_SHARING:
+                // Uses TEMPLATE_PREVIEW instead of TEMPLATE_RECORD. Since there is a issue that
+                // captured results being stretched when requested for recording on some models,
+                // it would be safer to request for preview, which is also better tested. More
+                // detail please see b/297167569.
             case PREVIEW:
             case IMAGE_ANALYSIS:
             default:
@@ -72,8 +76,10 @@
                         ? CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG :
                         CameraDevice.TEMPLATE_STILL_CAPTURE;
             case VIDEO_CAPTURE:
-            case STREAM_SHARING:
                 return CameraDevice.TEMPLATE_RECORD;
+            case STREAM_SHARING:
+                // Uses TEMPLATE_PREVIEW instead of TEMPLATE_RECORD to align with
+                // getSessionConfigTemplateType method. More detail please see b/297167569.
             case PREVIEW:
             case IMAGE_ANALYSIS:
             default:
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/VideoStabilizationUtil.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/VideoStabilizationUtil.java
new file mode 100644
index 0000000..b9f5eeb
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/VideoStabilizationUtil.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal;
+
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+
+/**
+ * A class that contains utility methods for video stabilization.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public final class VideoStabilizationUtil {
+
+    private VideoStabilizationUtil() {
+    }
+
+    /**
+     * Return true if the given camera characteristics support preview stabilization.
+     */
+    public static boolean isPreviewStabilizationSupported(
+            @NonNull CameraCharacteristicsCompat characteristicsCompat) {
+        if (Build.VERSION.SDK_INT < 33) {
+            return false;
+        }
+        int[] availableVideoStabilizationModes = characteristicsCompat.get(
+                CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES);
+        if (availableVideoStabilizationModes == null
+                || availableVideoStabilizationModes.length == 0) {
+            return false;
+        }
+        for (int mode : availableVideoStabilizationModes) {
+            if (mode == CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/FlashTooSlowQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/FlashTooSlowQuirk.java
index 8da23cc..2398ddf 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/FlashTooSlowQuirk.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/FlashTooSlowQuirk.java
@@ -33,11 +33,12 @@
  * Quirks that denotes the device has a slow flash sequence that could result in blurred pictures.
  *
  * <p>QuirkSummary
- *     Bug Id: 211474332, 286190938, 280221967
+ *     Bug Id: 211474332, 286190938, 280221967, 296814664, 296816175
  *     Description: When capturing still photos in auto flash mode, it needs more than 1 second to
  *     flash or capture actual photo after flash, and therefore it easily results in blurred or dark
  *     or overexposed pictures.
- *     Device(s): Pixel 3a / Pixel 3a XL, all models of Pixel 4 and 5, SM-A320
+ *     Device(s): Pixel 3a / Pixel 3a XL, all models of Pixel 4 and 5, SM-A320, Moto G20, Itel A48,
+ *     Realme C11 2021
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public class FlashTooSlowQuirk implements UseTorchAsFlashQuirk {
@@ -46,7 +47,10 @@
             "PIXEL 3A XL",
             "PIXEL 4", // includes Pixel 4 XL, 4A, and 4A (5g) too
             "PIXEL 5", // includes Pixel 5A too
-            "SM-A320"
+            "SM-A320",
+            "MOTO G(20)",
+            "ITEL L6006", // Itel A48
+            "RMX3231" // Realme C11 2021
     );
 
     static boolean load(@NonNull CameraCharacteristicsCompat cameraCharacteristics) {
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/InvalidVideoProfilesQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/InvalidVideoProfilesQuirk.java
index ba916eb..842e16c 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/InvalidVideoProfilesQuirk.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/InvalidVideoProfilesQuirk.java
@@ -30,18 +30,19 @@
  * Quirk denoting the video profile list returns by {@link EncoderProfiles} is invalid.
  *
  * <p>QuirkSummary
- *     Bug Id: 267727595, 278860860
- *     Description: When using {@link EncoderProfiles} on TP1A or TD1A builds of Android API 33,
+ *     Bug Id: 267727595, 278860860, 298951126, 298952500
+ *     Description: When using {@link EncoderProfiles} on some builds of Android API 33,
  *                  {@link EncoderProfiles#getVideoProfiles()} returns a list with size one, but
  *                  the single value in the list is null. This is not the expected behavior, and
  *                  makes {@link EncoderProfiles} lack of video information.
  *     Device(s): Pixel 4 and above pixel devices with TP1A or TD1A builds (API 33), Samsung devices
- *                with TP1A build (API 33).
+ *                 with TP1A build (API 33), Xiaomi devices with TKQ1 build (API 33), OnePlus and
+ *                 Oppo devices with API 33 build.
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public class InvalidVideoProfilesQuirk implements Quirk {
 
-    static final List<String> AFFECTED_PIXEL_MODELS = Arrays.asList(
+    private static final List<String> AFFECTED_PIXEL_MODELS = Arrays.asList(
             "pixel 4",
             "pixel 4a",
             "pixel 4a (5g)",
@@ -55,8 +56,19 @@
             "pixel 7 pro"
     );
 
+    private static final List<String> AFFECTED_ONE_PLUS_MODELS = Arrays.asList(
+            "cph2417",
+            "cph2451"
+    );
+
+    private static final List<String> AFFECTED_OPPO_MODELS = Arrays.asList(
+            "cph2437",
+            "cph2525"
+    );
+
     static boolean load() {
-        return isAffectedSamsungDevices() || isAffectedPixelDevices();
+        return isAffectedSamsungDevices() || isAffectedPixelDevices() || isAffectedXiaomiDevices()
+                || isAffectedOnePlusDevices() || isAffectedOppoDevices();
     }
 
     private static boolean isAffectedSamsungDevices() {
@@ -67,10 +79,31 @@
         return isAffectedPixelModel() && isAffectedPixelBuild();
     }
 
+    private static boolean isAffectedOnePlusDevices() {
+        return isAffectedOnePlusModel() && isAPI33();
+    }
+
+    private static boolean isAffectedOppoDevices() {
+        return isAffectedOppoModel() && isAPI33();
+    }
+
+    private static boolean isAffectedXiaomiDevices() {
+        return ("redmi".equalsIgnoreCase(Build.BRAND) || "xiaomi".equalsIgnoreCase(Build.BRAND))
+                && isTkq1Build();
+    }
+
     private static boolean isAffectedPixelModel() {
         return AFFECTED_PIXEL_MODELS.contains(Build.MODEL.toLowerCase(Locale.ROOT));
     }
 
+    private static boolean isAffectedOnePlusModel() {
+        return AFFECTED_ONE_PLUS_MODELS.contains(Build.MODEL.toLowerCase(Locale.ROOT));
+    }
+
+    private static boolean isAffectedOppoModel() {
+        return AFFECTED_OPPO_MODELS.contains(Build.MODEL.toLowerCase(Locale.ROOT));
+    }
+
     private static boolean isAffectedPixelBuild() {
         return isTp1aBuild() || isTd1aBuild();
     }
@@ -82,4 +115,12 @@
     private static boolean isTd1aBuild() {
         return Build.ID.toLowerCase(Locale.ROOT).startsWith("td1a");
     }
+
+    private static boolean isTkq1Build() {
+        return Build.ID.toLowerCase(Locale.ROOT).startsWith("tkq1");
+    }
+
+    private static boolean isAPI33() {
+        return Build.VERSION.SDK_INT == 33;
+    }
 }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/TorchIsClosedAfterImageCapturingQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/TorchIsClosedAfterImageCapturingQuirk.java
index cbff938..4b991ff 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/TorchIsClosedAfterImageCapturingQuirk.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/TorchIsClosedAfterImageCapturingQuirk.java
@@ -29,20 +29,21 @@
  * <p>QuirkSummary
  *     Bug Id: 228272227
  *     Description: The Torch is unexpectedly turned off after taking a picture.
- *     Device(s): Redmi 4X, Redmi 5A, Redmi Note 5, Mi A1, Mi A2, Mi A2 lite and Redmi 6 Pro.
+ *     Device(s): Redmi 4X, Redmi 5A, Redmi Note 5 (Pro), Mi A1, Mi A2, Mi A2 lite and Redmi 6 Pro.
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public class TorchIsClosedAfterImageCapturingQuirk implements Quirk {
 
     // List of devices with the issue. See b/228272227.
     public static final List<String> BUILD_MODELS = Arrays.asList(
-            "mi a1",        // Xiaomi Mi A1
-            "mi a2",        // Xiaomi Mi A2
-            "mi a2 lite",   // Xiaomi Mi A2 Lite
-            "redmi 4x",     // Xiaomi Redmi 4X
-            "redmi 5a",     // Xiaomi Redmi 5A
-            "redmi note 5", // Xiaomi Redmi Note 5
-            "redmi 6 pro"   // Xiaomi Redmi 6 Pro
+            "mi a1",            // Xiaomi Mi A1
+            "mi a2",            // Xiaomi Mi A2
+            "mi a2 lite",       // Xiaomi Mi A2 Lite
+            "redmi 4x",         // Xiaomi Redmi 4X
+            "redmi 5a",         // Xiaomi Redmi 5A
+            "redmi note 5",     // Xiaomi Redmi Note 5
+            "redmi note 5 pro", // Xiaomi Redmi Note 5 Pro
+            "redmi 6 pro"       // Xiaomi Redmi 6 Pro
     );
 
     static boolean load() {
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
index a0447b4..8a876ca 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
@@ -17,12 +17,12 @@
 package androidx.camera.camera2.internal;
 
 import static android.hardware.camera2.CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES;
-
+import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF;
+import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON;
+import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION;
 import static androidx.camera.core.DynamicRange.HLG_10_BIT;
 import static androidx.camera.core.DynamicRange.SDR;
-
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -678,6 +678,40 @@
         assertThat(resultFpsRanges1).isEmpty();
     }
 
+    /**
+     * Test for preview stabilization.
+     */
+    @Test
+    public void cameraInfo_isPreviewStabilizationSupported()
+            throws CameraAccessExceptionCompat {
+        init(/* hasAvailableCapabilities = */ false);
+
+        // Camera0
+        Camera2CameraInfoImpl cameraInfo0 = new Camera2CameraInfoImpl(CAMERA0_ID,
+                mCameraManagerCompat);
+
+
+        if (Build.VERSION.SDK_INT >= 33) {
+            assertThat(cameraInfo0.isPreviewStabilizationSupported()).isTrue();
+        } else {
+            assertThat(cameraInfo0.isPreviewStabilizationSupported()).isFalse();
+        }
+        assertThat(cameraInfo0.isVideoStabilizationSupported()).isTrue();
+
+        // Camera1
+        Camera2CameraInfoImpl cameraInfo1 = new Camera2CameraInfoImpl(CAMERA1_ID,
+                mCameraManagerCompat);
+
+        assertThat(cameraInfo1.isPreviewStabilizationSupported()).isFalse();
+        assertThat(cameraInfo0.isVideoStabilizationSupported()).isTrue();
+
+        // Camera2
+        Camera2CameraInfoImpl cameraInfo2 = new Camera2CameraInfoImpl(CAMERA2_ID,
+                mCameraManagerCompat);
+        assertThat(cameraInfo2.isPreviewStabilizationSupported()).isFalse();
+        assertThat(cameraInfo2.isVideoStabilizationSupported()).isFalse();
+    }
+
     @Test
     public void cameraInfo_checkDefaultCameraIntrinsicZoomRatio()
             throws CameraAccessExceptionCompat {
@@ -804,6 +838,24 @@
                     CAMERA0_DYNAMIC_RANGE_PROFILES);
         }
 
+        // Add video stabilization modes
+        if (Build.VERSION.SDK_INT >= 33) {
+            shadowCharacteristics0.set(
+                    CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
+                    new int[] {
+                            CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+                            CONTROL_VIDEO_STABILIZATION_MODE_ON,
+                            CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION
+                    });
+        } else {
+            shadowCharacteristics0.set(
+                    CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
+                    new int[] {
+                            CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+                            CONTROL_VIDEO_STABILIZATION_MODE_ON
+                    });
+        }
+
         // Mock the request capability
         if (hasAvailableCapabilities) {
             shadowCharacteristics0.set(REQUEST_AVAILABLE_CAPABILITIES,
@@ -837,6 +889,14 @@
         shadowCharacteristics1.set(
                 CameraCharacteristics.FLASH_INFO_AVAILABLE, CAMERA1_FLASH_INFO_BOOLEAN);
 
+        // Add video stabilization modes
+        shadowCharacteristics1.set(
+                CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
+                new int[] {
+                        CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+                        CONTROL_VIDEO_STABILIZATION_MODE_ON
+                });
+
         // Mock the supported resolutions
         {
             int formatPrivate = ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE;
@@ -887,6 +947,13 @@
                 CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,
                 CAMERA2_AE_FPS_RANGES);
 
+        // Add video stabilization modes
+        shadowCharacteristics2.set(
+                CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
+                new int[] {
+                        CONTROL_VIDEO_STABILIZATION_MODE_OFF
+                });
+
         // Add the camera to the camera service
         ((ShadowCameraManager)
                 Shadow.extract(
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2SessionOptionUnpackerTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2SessionOptionUnpackerTest.java
index 76be504..def80e4 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2SessionOptionUnpackerTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2SessionOptionUnpackerTest.java
@@ -34,11 +34,16 @@
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.Preview;
 import androidx.camera.core.impl.CameraCaptureCallback;
+import androidx.camera.core.impl.CaptureConfig;
 import androidx.camera.core.impl.Config;
 import androidx.camera.core.impl.Config.OptionPriority;
 import androidx.camera.core.impl.ImageCaptureConfig;
 import androidx.camera.core.impl.PreviewConfig;
 import androidx.camera.core.impl.SessionConfig;
+import androidx.camera.core.impl.stabilization.StabilizationMode;
+import androidx.camera.video.Recorder;
+import androidx.camera.video.VideoCapture;
+import androidx.camera.video.impl.VideoCaptureConfig;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -169,6 +174,83 @@
                 .isEqualTo(CaptureRequest.TONEMAP_MODE_HIGH_QUALITY);
     }
 
+    @Test
+    public void unpackerExtractsPreviewStabilizationMode() {
+        ReflectionHelpers.setStaticField(Build.class, "MANUFACTURER", "Google");
+        ReflectionHelpers.setStaticField(Build.class, "DEVICE", "sunfish");
+
+        Preview.Builder previewConfigBuilder =
+                new Preview.Builder().setPreviewStabilizationEnabled(true);
+
+        PreviewConfig useCaseConfig = previewConfigBuilder.getUseCaseConfig();
+
+        SessionConfig.Builder sessionBuilder = new SessionConfig.Builder();
+        mUnpacker.unpack(RESOLUTION_VGA, useCaseConfig, sessionBuilder);
+        SessionConfig sessionConfig = sessionBuilder.build();
+
+        CaptureConfig captureConfig = sessionConfig.getRepeatingCaptureConfig();
+
+        assertThat(captureConfig.getVideoStabilizationMode())
+                .isEqualTo(StabilizationMode.UNSPECIFIED);
+        assertThat(captureConfig.getPreviewStabilizationMode())
+                .isEqualTo(StabilizationMode.ON);
+    }
+
+    @Test
+    public void unpackerExtractsVideoStabilizationMode() {
+        ReflectionHelpers.setStaticField(Build.class, "MANUFACTURER", "Google");
+        ReflectionHelpers.setStaticField(Build.class, "DEVICE", "sunfish");
+
+        VideoCapture.Builder<Recorder> videoCaptureConfigBuilder =
+                new VideoCapture.Builder<>(new Recorder.Builder().build())
+                        .setVideoStabilizationEnabled(true);
+
+        VideoCaptureConfig<Recorder> useCaseConfig = videoCaptureConfigBuilder.getUseCaseConfig();
+
+        SessionConfig.Builder sessionBuilder = new SessionConfig.Builder();
+        mUnpacker.unpack(RESOLUTION_VGA, useCaseConfig, sessionBuilder);
+        SessionConfig sessionConfig = sessionBuilder.build();
+
+        CaptureConfig captureConfig = sessionConfig.getRepeatingCaptureConfig();
+
+        assertThat(captureConfig.getVideoStabilizationMode())
+                .isEqualTo(StabilizationMode.ON);
+        assertThat(captureConfig.getPreviewStabilizationMode())
+                .isEqualTo(StabilizationMode.UNSPECIFIED);
+    }
+
+    @Test
+    public void unpackerExtractsBothPreviewAndVideoStabilizationMode() {
+        ReflectionHelpers.setStaticField(Build.class, "MANUFACTURER", "Google");
+        ReflectionHelpers.setStaticField(Build.class, "DEVICE", "sunfish");
+
+        // unpack for preview
+        Preview.Builder previewConfigBuilder =
+                new Preview.Builder().setPreviewStabilizationEnabled(true);
+
+        PreviewConfig previewConfig = previewConfigBuilder.getUseCaseConfig();
+
+        SessionConfig.Builder sessionBuilder = new SessionConfig.Builder();
+        mUnpacker.unpack(RESOLUTION_VGA, previewConfig, sessionBuilder);
+
+        // unpack for preview
+        VideoCapture.Builder<Recorder> videoCaptureConfigBuilder =
+                new VideoCapture.Builder<>(new Recorder.Builder().build())
+                        .setVideoStabilizationEnabled(true);
+
+        VideoCaptureConfig<Recorder> videoCaptureConfig =
+                videoCaptureConfigBuilder.getUseCaseConfig();
+
+        mUnpacker.unpack(RESOLUTION_VGA, videoCaptureConfig, sessionBuilder);
+        SessionConfig sessionConfig = sessionBuilder.build();
+        CaptureConfig captureConfig = sessionConfig.getRepeatingCaptureConfig();
+
+        assertThat(captureConfig.getVideoStabilizationMode())
+                .isEqualTo(StabilizationMode.ON);
+        assertThat(captureConfig.getPreviewStabilizationMode())
+                .isEqualTo(StabilizationMode.ON);
+    }
+
     private OptionPriority getCaptureRequestOptionPriority(Config config,
             CaptureRequest.Key<?> key) {
         Config.Option<?> option = Camera2ImplConfig.createCaptureRequestOption(key);
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
index 1c83442..496663c 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
@@ -17,12 +17,10 @@
 package androidx.camera.camera2.internal;
 
 import static android.os.Build.VERSION.SDK_INT;
-
 import static androidx.camera.camera2.internal.StreamUseCaseUtil.STREAM_USE_CASE_STREAM_SPEC_OPTION;
 import static androidx.camera.core.DynamicRange.BIT_DEPTH_10_BIT;
 import static androidx.camera.core.ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY;
 import static androidx.camera.core.ImageCapture.CAPTURE_MODE_ZERO_SHUTTER_LAG;
-
 import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertTrue;
 
@@ -198,14 +196,16 @@
     public void shouldUseStreamUseCase_cameraModeNotSupported() {
         assertFalse(StreamUseCaseUtil.shouldUseStreamUseCase(
                 SupportedSurfaceCombination.FeatureSettings.of(CameraMode.CONCURRENT_CAMERA,
-                        DynamicRange.BIT_DEPTH_8_BIT)));
+                        DynamicRange.BIT_DEPTH_8_BIT,
+                        false)));
     }
 
     @Test
     public void shouldUseStreamUseCase_bitDepthNotSupported() {
         assertFalse(StreamUseCaseUtil.shouldUseStreamUseCase(
                 SupportedSurfaceCombination.FeatureSettings.of(CameraMode.DEFAULT,
-                        BIT_DEPTH_10_BIT)));
+                        BIT_DEPTH_10_BIT,
+                        false)));
     }
 
     @Test
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
index c5d0844..2db0d98 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
@@ -212,7 +212,7 @@
         GuaranteedConfigurationsUtil.getLegacySupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
                     it.surfaceConfigList
                 )
             ).isTrue()
@@ -228,7 +228,7 @@
         GuaranteedConfigurationsUtil.getLimitedSupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
                     it.surfaceConfigList
                 )
             ).isFalse()
@@ -244,7 +244,7 @@
         GuaranteedConfigurationsUtil.getFullSupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
                     it.surfaceConfigList
                 )
             ).isFalse()
@@ -260,7 +260,7 @@
         GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
                     it.surfaceConfigList
                 )
             ).isFalse()
@@ -278,7 +278,7 @@
         GuaranteedConfigurationsUtil.getLimitedSupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
                     it.surfaceConfigList
                 )
             ).isTrue()
@@ -296,7 +296,7 @@
         GuaranteedConfigurationsUtil.getFullSupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
                     it.surfaceConfigList
                 )
             ).isFalse()
@@ -314,7 +314,7 @@
         GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
                     it.surfaceConfigList
                 )
             ).isFalse()
@@ -332,7 +332,7 @@
         GuaranteedConfigurationsUtil.getFullSupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
                     it.surfaceConfigList
                 )
             ).isTrue()
@@ -350,7 +350,7 @@
         GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
                     it.surfaceConfigList
                 )
             ).isFalse()
@@ -369,7 +369,7 @@
         GuaranteedConfigurationsUtil.getLimitedSupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
                     it.surfaceConfigList
                 )
             ).isTrue()
@@ -388,7 +388,7 @@
         GuaranteedConfigurationsUtil.getLegacySupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
                     it.surfaceConfigList
                 )
             ).isTrue()
@@ -407,7 +407,7 @@
         GuaranteedConfigurationsUtil.getFullSupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
                     it.surfaceConfigList
                 )
             ).isTrue()
@@ -426,7 +426,7 @@
         GuaranteedConfigurationsUtil.getRAWSupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
                     it.surfaceConfigList
                 )
             ).isTrue()
@@ -444,7 +444,7 @@
         GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT),
+                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, false),
                     it.surfaceConfigList
                 )
             ).isTrue()
@@ -465,7 +465,7 @@
         GuaranteedConfigurationsUtil.getConcurrentSupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    FeatureSettings.of(CameraMode.CONCURRENT_CAMERA, BIT_DEPTH_8_BIT),
+                    FeatureSettings.of(CameraMode.CONCURRENT_CAMERA, BIT_DEPTH_8_BIT, false),
                     it.surfaceConfigList
                 )
             ).isTrue()
@@ -490,7 +490,7 @@
             assertThat(
                 supportedSurfaceCombination.checkSupported(
                     FeatureSettings.of(
-                        CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA, BIT_DEPTH_8_BIT
+                        CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA, BIT_DEPTH_8_BIT, false
                     ),
                     it.surfaceConfigList
                 )
@@ -498,6 +498,25 @@
         }
     }
 
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    fun checkPreviewStabilizationSurfaceCombinationSupportedWhenEnabled() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
+        )
+        val supportedSurfaceCombination = SupportedSurfaceCombination(
+            context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
+        )
+        GuaranteedConfigurationsUtil.getPreviewStabilizationSupportedCombinationList().forEach {
+            assertThat(
+                supportedSurfaceCombination.checkSupported(
+                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_8_BIT, true),
+                    it.surfaceConfigList
+                )
+            ).isTrue()
+        }
+    }
+
     // //////////////////////////////////////////////////////////////////////////////////////////
     //
     // Surface config transformation tests
@@ -1451,7 +1470,8 @@
         val resultPair = supportedSurfaceCombination.getSuggestedStreamSpecifications(
             cameraMode,
             attachedSurfaceInfoList,
-            useCaseConfigToOutputSizesMap
+            useCaseConfigToOutputSizesMap,
+            false
         )
         val suggestedStreamSpecsForNewUseCases = resultPair.first
         val suggestedStreamSpecsForOldSurfaces = resultPair.second
@@ -1564,7 +1584,7 @@
         GuaranteedConfigurationsUtil.get10BitSupportedCombinationList().forEach {
             assertThat(
                 supportedSurfaceCombination.checkSupported(
-                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_10_BIT),
+                    FeatureSettings.of(CameraMode.DEFAULT, BIT_DEPTH_10_BIT, false),
                     it.surfaceConfigList
                 )
             ).isTrue()
@@ -3327,6 +3347,19 @@
                 set(CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES, uc)
             }
 
+            val vs: IntArray
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+                vs = intArrayOf(
+                    CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+                    CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_ON,
+                    CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION)
+            } else {
+                vs = intArrayOf(
+                    CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_OFF,
+                    CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_ON)
+            }
+            set(CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES, vs)
+
             capabilities?.let {
                 set(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES, it)
             }
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/quirk/FlashTooSlowQuirkTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/quirk/FlashTooSlowQuirkTest.kt
index 4f156fc..c2f4239 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/quirk/FlashTooSlowQuirkTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/quirk/FlashTooSlowQuirkTest.kt
@@ -57,6 +57,9 @@
             arrayOf("sm-a320f", CameraCharacteristics.LENS_FACING_BACK, true),
             arrayOf("SM-A320FL", CameraCharacteristics.LENS_FACING_BACK, true),
             arrayOf("Samsung S7", CameraCharacteristics.LENS_FACING_BACK, false),
+            arrayOf("moto g(20)", CameraCharacteristics.LENS_FACING_BACK, true),
+            arrayOf("itel l6006", CameraCharacteristics.LENS_FACING_BACK, true),
+            arrayOf("rmx3231", CameraCharacteristics.LENS_FACING_BACK, true),
         )
     }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java
index 105bde1..4d7519d 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraInfo.java
@@ -298,6 +298,17 @@
     }
 
     /**
+     * Returns {@link PreviewCapabilities} to query preview stream related device capability.
+     *
+     * @return {@link PreviewCapabilities}
+     */
+    @NonNull
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    default PreviewCapabilities getPreviewCapabilities() {
+        return PreviewCapabilities.EMPTY;
+    }
+
+    /**
      * Returns if {@link ImageFormat#PRIVATE} reprocessing is supported on the device.
      *
      * @return true if supported, otherwise false.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
index a7686c7..c39b5cb 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
@@ -41,6 +41,7 @@
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_CAMERA_SELECTOR;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_CAPTURE_TYPE;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_HIGH_RESOLUTION_DISABLED;
+import static androidx.camera.core.impl.UseCaseConfig.OPTION_PREVIEW_STABILIZATION_MODE;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_TARGET_FRAME_RATE;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_ZSL_DISABLED;
 import static androidx.camera.core.impl.utils.Threads.checkMainThread;
@@ -86,6 +87,7 @@
 import androidx.camera.core.impl.StreamSpec;
 import androidx.camera.core.impl.UseCaseConfig;
 import androidx.camera.core.impl.UseCaseConfigFactory;
+import androidx.camera.core.impl.stabilization.StabilizationMode;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.core.internal.TargetConfig;
 import androidx.camera.core.internal.ThreadConfig;
@@ -268,6 +270,7 @@
         SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config,
                 streamSpec.getResolution());
         sessionConfigBuilder.setExpectedFrameRateRange(streamSpec.getExpectedFrameRateRange());
+        sessionConfigBuilder.setPreviewStabilization(config.getPreviewStabilizationMode());
         if (streamSpec.getImplementationOptions() != null) {
             sessionConfigBuilder.addImplementationOptions(streamSpec.getImplementationOptions());
         }
@@ -670,6 +673,14 @@
     }
 
     /**
+     * Returns whether video stabilization is enabled for preview stream.
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    public boolean isPreviewStabilizationEnabled() {
+        return getCurrentConfig().getPreviewStabilizationMode() == StabilizationMode.ON;
+    }
+
+    /**
      * A interface implemented by the application to provide a {@link Surface} for {@link Preview}.
      *
      * <p> This interface is implemented by the application to provide a {@link Surface}. This
@@ -1148,6 +1159,20 @@
             return this;
         }
 
+        /**
+         * Enable preview stabilization.
+         *
+         * @param enabled True if enable, otherwise false.
+         * @return the current Builder.
+         */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public Builder setPreviewStabilizationEnabled(boolean enabled) {
+            getMutableConfig().insertOption(OPTION_PREVIEW_STABILIZATION_MODE,
+                    enabled ? StabilizationMode.ON : StabilizationMode.OFF);
+            return this;
+        }
+
         // Implementations of UseCaseConfig.Builder default methods
 
         @RestrictTo(Scope.LIBRARY_GROUP)
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/PreviewCapabilities.java b/camera/camera-core/src/main/java/androidx/camera/core/PreviewCapabilities.java
new file mode 100644
index 0000000..283b013
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/PreviewCapabilities.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core;
+
+import android.hardware.camera2.CaptureRequest;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+
+/**
+ * PreviewCapabilities is used to query preview stream capabilities on the device.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public interface PreviewCapabilities {
+
+    /**
+     * Returns if preview stabilization is supported on the device.
+     *
+     * @return true if
+     * {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION} is supported,
+     * otherwise false.
+     *
+     * @see CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE
+     */
+    boolean isStabilizationSupported();
+
+
+    /** An empty implementation. */
+    @NonNull
+    PreviewCapabilities EMPTY = new PreviewCapabilities() {
+        @Override
+        public boolean isStabilizationSupported() {
+            return false;
+        }
+    };
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraDeviceSurfaceManager.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraDeviceSurfaceManager.java
index 9abb314..540f454 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraDeviceSurfaceManager.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraDeviceSurfaceManager.java
@@ -83,6 +83,7 @@
      * @param newUseCaseConfigsSupportedSizeMap map of configurations of the use cases to the
      *                                          supported output sizes list that will be given a
      *                                          suggested stream specification
+     * @param isPreviewStabilizationOn          whether the preview stabilization is enabled.
      * @return map of suggested stream specifications for given use cases
      * @throws IllegalStateException    if not initialized
      * @throws IllegalArgumentException if {@code newUseCaseConfigs} is an empty list, if
@@ -96,5 +97,6 @@
             @CameraMode.Mode int cameraMode,
             @NonNull String cameraId,
             @NonNull List<AttachedSurfaceInfo> existingSurfaces,
-            @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap);
+            @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap,
+            boolean isPreviewStabilizationOn);
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java
index e6a1e9b..d78c73a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java
@@ -18,6 +18,7 @@
 
 import android.graphics.ImageFormat;
 import android.graphics.PixelFormat;
+import android.hardware.camera2.CaptureRequest;
 import android.util.Size;
 
 import androidx.annotation.NonNull;
@@ -106,6 +107,27 @@
     Set<DynamicRange> getSupportedDynamicRanges();
 
     /**
+     * Returns if preview stabilization is supported on the device.
+     *
+     * @return true if
+     * {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION} is supported,
+     * otherwise false.
+     *
+     * @see CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE
+     */
+    boolean isPreviewStabilizationSupported();
+
+    /**
+     * Returns if video stabilization is supported on the device.
+     *
+     * @return true if {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE_ON} is supported,
+     * otherwise false.
+     *
+     * @see CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE
+     */
+    boolean isVideoStabilizationSupported();
+
+    /**
      * Gets the underlying implementation instance which could be cast into an implementation
      * specific class for further use in implementation module. Returns <code>this</code> if this
      * instance is the implementation instance.
@@ -115,7 +137,6 @@
         return this;
     }
 
-
     /** {@inheritDoc} */
     @NonNull
     @Override
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CaptureConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CaptureConfig.java
index 6cf83bf..600629c 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CaptureConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CaptureConfig.java
@@ -25,6 +25,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
+import androidx.camera.core.impl.stabilization.StabilizationMode;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -78,6 +79,12 @@
 
     final Range<Integer> mExpectedFrameRateRange;
 
+    @StabilizationMode.Mode
+    final int mPreviewStabilizationMode;
+
+    @StabilizationMode.Mode
+    final int mVideoStabilizationMode;
+
     /** The camera capture callback for a {@link CameraCaptureSession}. */
     final List<CameraCaptureCallback> mCameraCaptureCallbacks;
 
@@ -105,6 +112,9 @@
      * @param templateType           The template for parameters of the CaptureRequest. This
      *                               must match the
      *                               constants defined by {@link CameraDevice}.
+     * @param expectedFrameRateRange The expected frame rate range.
+     * @param previewStabilizationMode The preview stabilization mode.
+     * @param videoStabilizationMode The video stabilization mode.
      * @param cameraCaptureCallbacks All camera capture callbacks.
      * @param cameraCaptureResult     The {@link CameraCaptureResult} for reprocessing capture
      *                               request.
@@ -114,6 +124,8 @@
             Config implementationOptions,
             int templateType,
             @NonNull Range<Integer> expectedFrameRateRange,
+            int previewStabilizationMode,
+            int videoStabilizationMode,
             List<CameraCaptureCallback> cameraCaptureCallbacks,
             boolean useRepeatingSurface,
             @NonNull TagBundle tagBundle,
@@ -122,6 +134,8 @@
         mImplementationOptions = implementationOptions;
         mTemplateType = templateType;
         mExpectedFrameRateRange = expectedFrameRateRange;
+        mPreviewStabilizationMode = previewStabilizationMode;
+        mVideoStabilizationMode = videoStabilizationMode;
         mCameraCaptureCallbacks = Collections.unmodifiableList(cameraCaptureCallbacks);
         mUseRepeatingSurface = useRepeatingSurface;
         mTagBundle = tagBundle;
@@ -169,6 +183,16 @@
         return mExpectedFrameRateRange;
     }
 
+    @StabilizationMode.Mode
+    public int getPreviewStabilizationMode() {
+        return mPreviewStabilizationMode;
+    }
+
+    @StabilizationMode.Mode
+    public int getVideoStabilizationMode() {
+        return mVideoStabilizationMode;
+    }
+
     public boolean isUseRepeatingSurface() {
         return mUseRepeatingSurface;
     }
@@ -206,6 +230,10 @@
         private MutableConfig mImplementationOptions = MutableOptionsBundle.create();
         private int mTemplateType = TEMPLATE_TYPE_NONE;
         private Range<Integer> mExpectedFrameRateRange = StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED;
+        @StabilizationMode.Mode
+        private int mPreviewStabilizationMode = StabilizationMode.UNSPECIFIED;
+        @StabilizationMode.Mode
+        private int mVideoStabilizationMode = StabilizationMode.UNSPECIFIED;
         private List<CameraCaptureCallback> mCameraCaptureCallbacks = new ArrayList<>();
         private boolean mUseRepeatingSurface = false;
         private MutableTagBundle mMutableTagBundle = MutableTagBundle.create();
@@ -220,6 +248,8 @@
             mImplementationOptions = MutableOptionsBundle.from(base.mImplementationOptions);
             mTemplateType = base.mTemplateType;
             mExpectedFrameRateRange = base.mExpectedFrameRateRange;
+            mVideoStabilizationMode = base.mVideoStabilizationMode;
+            mPreviewStabilizationMode = base.mPreviewStabilizationMode;
             mCameraCaptureCallbacks.addAll(base.getCameraCaptureCallbacks());
             mUseRepeatingSurface = base.isUseRepeatingSurface();
             mMutableTagBundle = MutableTagBundle.from(base.getTagBundle());
@@ -290,6 +320,26 @@
         }
 
         /**
+         * Set the preview stabilization mode of the CaptureConfig.
+         * @param mode {@link StabilizationMode}
+         */
+        public void setPreviewStabilization(@StabilizationMode.Mode int mode) {
+            if (mode != StabilizationMode.UNSPECIFIED) {
+                mPreviewStabilizationMode = mode;
+            }
+        }
+
+        /**
+         * Set the video stabilization mode of the CaptureConfig.
+         * @param mode {@link StabilizationMode}
+         */
+        public void setVideoStabilization(@StabilizationMode.Mode int mode) {
+            if (mode != StabilizationMode.UNSPECIFIED) {
+                mVideoStabilizationMode = mode;
+            }
+        }
+
+        /**
          * Adds a {@link CameraCaptureCallback} callback.
          */
         public void addCameraCaptureCallback(@NonNull CameraCaptureCallback cameraCaptureCallback) {
@@ -416,6 +466,8 @@
                     OptionsBundle.from(mImplementationOptions),
                     mTemplateType,
                     mExpectedFrameRateRange,
+                    mPreviewStabilizationMode,
+                    mVideoStabilizationMode,
                     new ArrayList<>(mCameraCaptureCallbacks),
                     mUseRepeatingSurface,
                     TagBundle.from(mMutableTagBundle),
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/Config.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/Config.java
index ab1314e..be3b3b8 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/Config.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/Config.java
@@ -46,8 +46,8 @@
      * Returns whether this configuration contains the supplied option.
      *
      * @param id The {@link Option} to search for in this configuration.
-     * @return <code>true</code> if this configuration contains the supplied option; <code>false
-     * </code> otherwise.
+     * @return {@code true} if this configuration contains the supplied option; {@code false}
+     * otherwise.
      */
     boolean containsOption(@NonNull Option<?> id);
 
@@ -77,7 +77,7 @@
      * @param valueIfMissing The value to return if the specified {@link Option} does not exist in
      *                       this configuration.
      * @param <ValueT>       The type for the value associated with the supplied {@link Option}.
-     * @return The value stored in this configuration, or <code>valueIfMissing</code> if it does
+     * @return The value stored in this configuration, or {@code valueIfMissing} if it does
      * not exist.
      */
     @Nullable
@@ -116,12 +116,10 @@
      *                       option such as \"<code>
      *                       camerax.core.example</code>\".
      * @param matcher        A callback used to receive results of the search. Results will be
-     *                       sent to
-     *                       {@link OptionMatcher#onOptionMatched(Option)} in the order in which
-     *                       they are found inside
-     *                       this configuration. Subsequent results will continue to be sent as
-     *                       long as {@link
-     *                       OptionMatcher#onOptionMatched(Option)} returns <code>true</code>.
+     *                       sent to {@link OptionMatcher#onOptionMatched(Option)} in the order
+     *                       in which they are found inside this configuration. Subsequent
+     *                       results will continue to be sent as long as {@link
+     *                       OptionMatcher#onOptionMatched(Option)} returns {@code true}.
      */
     void findOptions(@NonNull String idSearchString, @NonNull OptionMatcher matcher);
 
@@ -199,8 +197,7 @@
          * @param valueClass The class of the value stored by this option.
          * @param <T>        The type of the value stored by this option.
          * @param token      An optional, type-erased object for storing more context for this
-         *                   specific
-         *                   option. Generally this object should have static scope and be
+         *                   specific option. Generally this object should have static scope and be
          *                   immutable.
          * @return An {@link Option} object which can be used to store/retrieve values from a {@link
          * Config}.
@@ -250,11 +247,14 @@
      */
     enum OptionPriority {
         /**
-         * Should only be used externally by apps. It takes precedence over any other option
-         * values at the risk of causing unexpected behavior.
+         * It takes precedence over any other option values at the risk of causing unexpected
+         * behavior.
          *
-         * <p>This should not used internally in CameraX. It conflicts when merging different
-         * values set to ALWAY_OVERRIDE.
+         * <p>If the same option is already set, the option with this priority will overwrite the
+         * value.
+         *
+         * <p>This priority should only be used to explicitly specify an option, such as used by
+         * {@code Camera2Interop} or {@code Camera2CameraControl}, and should be used with caution.
          */
         ALWAYS_OVERRIDE,
 
@@ -262,15 +262,17 @@
          * It's a required option value in order to achieve expected CameraX behavior. It takes
          * precedence over {@link #OPTIONAL} option values.
          *
-         * <p>If apps set ALWAYS_OVERRIDE options, it'll override REQUIRED option values and can
-         * potentially cause unexpected behaviors. It conflicts when merging different values set
-         * to REQUIRED.
+         * <p>If two values are set to the same option, the value with {@link #ALWAYS_OVERRIDE}
+         * priority will overwrite this priority and can potentially cause unexpected behaviors.
+         *
+         * <p>If two values are set to the same option with this priority, it might indicate a
+         * programming error internally and an exception will be thrown when merging the configs.
          */
         REQUIRED,
 
         /**
          * The lowest priority, it can be overridden by any other option value. When two option
-         * values are set as OPTIONAL, the newer value takes precedence over the old one.
+         * values are set with this priority, the newer value takes precedence over the old one.
          */
         OPTIONAL
     }
@@ -278,35 +280,25 @@
     /**
      * Returns if values with these {@link OptionPriority} conflict or not.
      *
-     * Currently it is not allowed to have different values with same ALWAYS_OVERRIDE
-     * priority or to have different values with same REQUIRED priority.
+     * <p>Currently it is not allowed the same option to have different values with priority
+     * {@link OptionPriority#REQUIRED}.
      */
     static boolean hasConflict(@NonNull OptionPriority priority1,
             @NonNull OptionPriority priority2) {
-        if (priority1 == OptionPriority.ALWAYS_OVERRIDE
-                && priority2 == OptionPriority.ALWAYS_OVERRIDE) {
-            return true;
-        }
-
-        if (priority1 == OptionPriority.REQUIRED
-                && priority2 == OptionPriority.REQUIRED) {
-            return true;
-        }
-
-        return false;
+        return priority1 == OptionPriority.REQUIRED
+                && priority2 == OptionPriority.REQUIRED;
     }
 
     /**
-     * Merges two configs
+     * Merges two configs.
      *
      * @param extendedConfig the extended config. The options in the extendedConfig will be applied
      *                       on top of the baseConfig based on the option priorities.
-     * @param baseConfig the base config
-     * @return a {@link MutableOptionsBundle} of the merged config
+     * @param baseConfig the base config.
+     * @return a {@link MutableOptionsBundle} of the merged config.
      */
     @NonNull
-    static Config mergeConfigs(@Nullable Config extendedConfig,
-            @Nullable Config baseConfig) {
+    static Config mergeConfigs(@Nullable Config extendedConfig, @Nullable Config baseConfig) {
         if (extendedConfig == null && baseConfig == null) {
             return OptionsBundle.emptyBundle();
         }
@@ -333,12 +325,12 @@
     /**
      * Merges a specific option value from two configs.
      *
-     * @param mergedConfig   the final output config
+     * @param mergedConfig   the final output config.
      * @param baseConfig     the base config contains the option value which might be overridden by
      *                       the corresponding option value in the extend config.
      * @param extendedConfig the extended config contains the option value which might override
      *                       the corresponding option value in the base config.
-     * @param opt            the option to merge
+     * @param opt            the option to merge.
      */
     static void mergeOptionValue(@NonNull MutableOptionsBundle mergedConfig,
             @NonNull Config baseConfig,
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java
index c389a1d..0bd54ec 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/ForwardingCameraInfo.java
@@ -27,6 +27,7 @@
 import androidx.camera.core.ExperimentalZeroShutterLag;
 import androidx.camera.core.ExposureState;
 import androidx.camera.core.FocusMeteringAction;
+import androidx.camera.core.PreviewCapabilities;
 import androidx.camera.core.ZoomState;
 import androidx.lifecycle.LiveData;
 
@@ -192,4 +193,20 @@
     public CameraSelector getCameraSelector() {
         return mCameraInfoInternal.getCameraSelector();
     }
+
+    @Override
+    public boolean isPreviewStabilizationSupported() {
+        return mCameraInfoInternal.isPreviewStabilizationSupported();
+    }
+
+    @Override
+    public boolean isVideoStabilizationSupported() {
+        return mCameraInfoInternal.isVideoStabilizationSupported();
+    }
+
+    @NonNull
+    @Override
+    public PreviewCapabilities getPreviewCapabilities() {
+        return mCameraInfoInternal.getPreviewCapabilities();
+    }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java
index fdd0390..a3d1fd8 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java
@@ -29,6 +29,7 @@
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.DynamicRange;
 import androidx.camera.core.Logger;
+import androidx.camera.core.impl.stabilization.StabilizationMode;
 import androidx.camera.core.internal.compat.workaround.SurfaceSorter;
 
 import com.google.auto.value.AutoValue;
@@ -431,6 +432,30 @@
         }
 
         /**
+         * Set the preview stabilization mode of the SessionConfig.
+         * @param mode {@link StabilizationMode}
+         */
+        @NonNull
+        public Builder setPreviewStabilization(@StabilizationMode.Mode int mode) {
+            if (mode != StabilizationMode.UNSPECIFIED) {
+                mCaptureConfigBuilder.setPreviewStabilization(mode);
+            }
+            return this;
+        }
+
+        /**
+         * Set the video stabilization mode of the SessionConfig.
+         * @param mode {@link StabilizationMode}
+         */
+        @NonNull
+        public Builder setVideoStabilization(@StabilizationMode.Mode int mode) {
+            if (mode != StabilizationMode.UNSPECIFIED) {
+                mCaptureConfigBuilder.setVideoStabilization(mode);
+            }
+            return this;
+        }
+
+        /**
          * Adds a tag to the SessionConfig with a key. For tracking the source.
          */
         @NonNull
@@ -748,6 +773,8 @@
             }
 
             setOrVerifyExpectFrameRateRange(captureConfig.getExpectedFrameRateRange());
+            setPreviewStabilizationMode(captureConfig.getPreviewStabilizationMode());
+            setVideoStabilizationMode(captureConfig.getVideoStabilizationMode());
 
             TagBundle tagBundle = sessionConfig.getRepeatingCaptureConfig().getTagBundle();
             mCaptureConfigBuilder.addAllTags(tagBundle);
@@ -810,6 +837,18 @@
             }
         }
 
+        private void setPreviewStabilizationMode(@StabilizationMode.Mode int mode) {
+            if (mode != StabilizationMode.UNSPECIFIED) {
+                mCaptureConfigBuilder.setPreviewStabilization(mode);
+            }
+        }
+
+        private void setVideoStabilizationMode(@StabilizationMode.Mode int mode) {
+            if (mode != StabilizationMode.UNSPECIFIED) {
+                mCaptureConfigBuilder.setVideoStabilization(mode);
+            }
+        }
+
         private List<DeferrableSurface> getSurfaces() {
             List<DeferrableSurface> surfaces = new ArrayList<>();
             for (OutputConfig outputConfig : mOutputConfigs) {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/UseCaseConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/UseCaseConfig.java
index 7e13965..435c05e 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/UseCaseConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/UseCaseConfig.java
@@ -24,6 +24,7 @@
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.ExtendableBuilder;
 import androidx.camera.core.UseCase;
+import androidx.camera.core.impl.stabilization.StabilizationMode;
 import androidx.camera.core.internal.TargetConfig;
 import androidx.camera.core.internal.UseCaseEventConfig;
 
@@ -100,6 +101,17 @@
     Option<UseCaseConfigFactory.CaptureType> OPTION_CAPTURE_TYPE = Option.create(
             "camerax.core.useCase.captureType", UseCaseConfigFactory.CaptureType.class);
 
+    /**
+     * Option: camerax.core.useCase.previewStabilizationMode
+     */
+    Option<Integer> OPTION_PREVIEW_STABILIZATION_MODE =
+            Option.create("camerax.core.useCase.previewStabilizationMode", int.class);
+
+    /**
+     * Option: camerax.core.useCase.videoStabilizationMode
+     */
+    Option<Integer> OPTION_VIDEO_STABILIZATION_MODE =
+            Option.create("camerax.core.useCase.videoStabilizationMode", int.class);
 
     // *********************************************************************************************
 
@@ -328,6 +340,23 @@
     }
 
     /**
+     * @return The preview stabilization mode of this UseCaseConfig.
+     */
+    @StabilizationMode.Mode
+    default int getPreviewStabilizationMode() {
+        return retrieveOption(OPTION_PREVIEW_STABILIZATION_MODE,
+                StabilizationMode.UNSPECIFIED);
+    }
+
+    /**
+     * @return The video stabilization mode of this UseCaseConfig.
+     */
+    @StabilizationMode.Mode
+    default int getVideoStabilizationMode() {
+        return retrieveOption(OPTION_VIDEO_STABILIZATION_MODE, StabilizationMode.UNSPECIFIED);
+    }
+
+    /**
      * Builder for a {@link UseCase}.
      *
      * @param <T> The type of the object which will be built by {@link #build()}.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/stabilization/StabilizationMode.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/stabilization/StabilizationMode.java
new file mode 100644
index 0000000..f5aab65
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/stabilization/StabilizationMode.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.impl.stabilization;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class for preview or video stabilization mode.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RequiresApi(21)
+public class StabilizationMode {
+
+    /* Not specified */
+    public static final int UNSPECIFIED = 0;
+    /* Off */
+    public static final int OFF = 1;
+    /* On */
+    public static final int ON = 2;
+
+    private StabilizationMode() {
+    }
+
+    /**
+     *
+     */
+    @IntDef({UNSPECIFIED, OFF, ON})
+    @Retention(RetentionPolicy.SOURCE)
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public @interface Mode {
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
index eff1afc..e998934 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
@@ -25,7 +25,6 @@
 import static androidx.camera.core.streamsharing.StreamSharing.isStreamSharing;
 import static androidx.core.util.Preconditions.checkArgument;
 import static androidx.core.util.Preconditions.checkState;
-
 import static java.util.Collections.emptyList;
 import static java.util.Objects.requireNonNull;
 
@@ -677,6 +676,7 @@
             SupportedOutputSizesSorter supportedOutputSizesSorter = new SupportedOutputSizesSorter(
                     cameraInfoInternal,
                     sensorRect != null ? rectToSize(sensorRect) : null);
+            boolean isPreviewStabilizationOn = false;
             for (UseCase useCase : newUseCases) {
                 ConfigPair configPair = configPairMap.get(useCase);
                 // Combine with default configuration.
@@ -687,6 +687,9 @@
                 configToSupportedSizesMap.put(combinedUseCaseConfig,
                         supportedOutputSizesSorter.getSortedSupportedOutputSizes(
                                 combinedUseCaseConfig));
+
+                // TODO(kailianc): extract the use case's video stabilization settings  and set
+                //  to isPreviewStabilizationOn
             }
 
             // Get suggested stream specifications and update the use case session configuration
@@ -695,7 +698,8 @@
                     mCameraDeviceSurfaceManager.getSuggestedStreamSpecs(
                             cameraMode,
                             cameraId, existingSurfaces,
-                            configToSupportedSizesMap);
+                            configToSupportedSizesMap,
+                            isPreviewStabilizationOn);
 
             for (Map.Entry<UseCaseConfig<?>, UseCase> entry : configToUseCaseMap.entrySet()) {
                 suggestedStreamSpecs.put(entry.getValue(),
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/LargeJpegImageQuirk.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/LargeJpegImageQuirk.java
index c6ad529..471095e 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/LargeJpegImageQuirk.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/LargeJpegImageQuirk.java
@@ -28,16 +28,16 @@
 
 /**
  * <p>QuirkSummary
- *     Bug Id: 288828159
+ *     Bug Id: 288828159, 299069235
  *     Description: Quirk required to check whether the captured JPEG image contains redundant
  *                  0's padding data. For example, Samsung A5 (2017) series devices have the
  *                  problem and result in the output JPEG image to be extremely large (about 32 MB).
- *     Device(s): Samsung Galaxy A5 (2017), A52, A70, A72 and S7 series devices
+ *     Device(s): Samsung Galaxy A5 (2017), A52, A70, A72, S7 series devices and Vivo S16 device
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public final class LargeJpegImageQuirk implements Quirk {
 
-    private static final Set<String> DEVICE_MODELS = new HashSet<>(Arrays.asList(
+    private static final Set<String> SAMSUNG_DEVICE_MODELS = new HashSet<>(Arrays.asList(
             // Samsung Galaxy A5 series devices
             "SM-A520F",
             "SM-A520L",
@@ -70,7 +70,22 @@
             "SM-S906B"
     ));
 
+    private static final Set<String> VIVO_DEVICE_MODELS = new HashSet<>(Arrays.asList(
+            // Vivo S16
+            "V2244A"
+    ));
+
     static boolean load() {
-        return DEVICE_MODELS.contains(Build.MODEL.toUpperCase(Locale.US));
+        return isSamsungProblematicDevice() || isVivoProblematicDevice();
+    }
+
+    private static boolean isSamsungProblematicDevice() {
+        return "Samsung".equalsIgnoreCase(Build.BRAND) && SAMSUNG_DEVICE_MODELS.contains(
+                Build.MODEL.toUpperCase(Locale.US));
+    }
+
+    private static boolean isVivoProblematicDevice() {
+        return "Vivo".equalsIgnoreCase(Build.BRAND) && VIVO_DEVICE_MODELS.contains(
+                Build.MODEL.toUpperCase(Locale.US));
     }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/InvalidJpegDataParser.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/InvalidJpegDataParser.java
index a9b122a..4a0d527 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/InvalidJpegDataParser.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/InvalidJpegDataParser.java
@@ -18,6 +18,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
 import androidx.camera.core.internal.compat.quirk.DeviceQuirks;
 import androidx.camera.core.internal.compat.quirk.LargeJpegImageQuirk;
 
@@ -41,12 +42,23 @@
             return bytes.length;
         }
 
+        int jfifEoiMarkEndPosition = getJfifEoiMarkEndPosition(bytes);
+
+        return jfifEoiMarkEndPosition != -1 ? jfifEoiMarkEndPosition : bytes.length;
+    }
+
+    /**
+     * Returns the end position of JFIF EOI mark. Returns -1 while JFIF EOI mark can't be found
+     * in the provided byte array.
+     */
+    @VisibleForTesting
+    public static int getJfifEoiMarkEndPosition(@NonNull byte[] bytes) {
         // Parses the JFIF segments from the start of the JPEG image data
         int markPosition = 0x2;
         while (true) {
             // Breaks the while-loop and return null if the mark byte can't be correctly found.
             if (markPosition + 4 > bytes.length || bytes[markPosition] != ((byte) 0xff)) {
-                return bytes.length;
+                return -1;
             }
 
             int segmentLength =
@@ -65,7 +77,7 @@
         while (true) {
             // Breaks the while-loop and return null if EOI mark can't be found
             if (eoiPosition + 2 > bytes.length) {
-                return bytes.length;
+                return -1;
             }
 
             if (bytes[eoiPosition] == ((byte) 0xff) && bytes[eoiPosition + 1] == ((byte) 0xd9)) {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCamera.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCamera.java
index 33a2a1e..7147eb4 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCamera.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCamera.java
@@ -21,7 +21,9 @@
 import static androidx.camera.core.impl.ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE;
 import static androidx.camera.core.impl.ImageInputConfig.OPTION_INPUT_DYNAMIC_RANGE;
 import static androidx.camera.core.impl.ImageOutputConfig.OPTION_CUSTOM_ORDERED_RESOLUTIONS;
+import static androidx.camera.core.impl.UseCaseConfig.OPTION_PREVIEW_STABILIZATION_MODE;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_SURFACE_OCCUPANCY_PRIORITY;
+import static androidx.camera.core.impl.UseCaseConfig.OPTION_VIDEO_STABILIZATION_MODE;
 import static androidx.camera.core.impl.utils.Threads.checkMainThread;
 import static androidx.camera.core.impl.utils.TransformUtils.getRotatedSize;
 import static androidx.camera.core.impl.utils.TransformUtils.rectToSize;
@@ -57,6 +59,7 @@
 import androidx.camera.core.impl.SessionConfig;
 import androidx.camera.core.impl.UseCaseConfig;
 import androidx.camera.core.impl.UseCaseConfigFactory;
+import androidx.camera.core.impl.stabilization.StabilizationMode;
 import androidx.camera.core.processing.SurfaceEdge;
 import androidx.camera.core.processing.SurfaceProcessorNode.OutConfig;
 
@@ -158,6 +161,21 @@
                     + " a dynamic range that satisfies all children.");
         }
         mutableConfig.insertOption(OPTION_INPUT_DYNAMIC_RANGE, dynamicRange);
+
+        // Merge Preview stabilization and video stabilization configs.
+        for (UseCase useCase : mChildren) {
+            if (useCase.getCurrentConfig().getVideoStabilizationMode()
+                    != StabilizationMode.UNSPECIFIED) {
+                mutableConfig.insertOption(OPTION_VIDEO_STABILIZATION_MODE,
+                        useCase.getCurrentConfig().getVideoStabilizationMode());
+            }
+
+            if (useCase.getCurrentConfig().getPreviewStabilizationMode()
+                    != StabilizationMode.UNSPECIFIED) {
+                mutableConfig.insertOption(OPTION_PREVIEW_STABILIZATION_MODE,
+                        useCase.getCurrentConfig().getPreviewStabilizationMode());
+            }
+        }
     }
 
     void bindChildren() {
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
index 57850a9..4d2bc37 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
@@ -780,6 +780,13 @@
         assertThat(preview.targetFrameRate).isEqualTo(Range(15, 30))
     }
 
+    @Test
+    fun canSetPreviewStabilization() {
+        val preview = Preview.Builder().setPreviewStabilizationEnabled(true)
+            .build()
+        assertThat(preview.isPreviewStabilizationEnabled).isTrue()
+    }
+
     private fun bindToLifecycleAndGetSurfaceRequest(): SurfaceRequest {
         return bindToLifecycleAndGetResult(null).first
     }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/impl/ConfigTest.java b/camera/camera-core/src/test/java/androidx/camera/core/impl/ConfigTest.java
index 96b086a..e78d689f 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/impl/ConfigTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/impl/ConfigTest.java
@@ -89,8 +89,8 @@
     }
 
     @Test
-    public void hasConflict_whenTwoValueAreALWAYSOVERRIDE() {
-        assertThat(Config.hasConflict(ALWAYS_OVERRIDE, ALWAYS_OVERRIDE)).isTrue();
+    public void noConflict_whenTwoValueAreALWAYSOVERRIDE() {
+        assertThat(Config.hasConflict(ALWAYS_OVERRIDE, ALWAYS_OVERRIDE)).isFalse();
     }
 
     @Test
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/impl/MutableOptionsBundleTest.java b/camera/camera-core/src/test/java/androidx/camera/core/impl/MutableOptionsBundleTest.java
index 1db4883..be0e91c 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/impl/MutableOptionsBundleTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/impl/MutableOptionsBundleTest.java
@@ -108,13 +108,18 @@
         assertThat(config2.retrieveOptionWithPriority(OPTION_2, OPTIONAL)).isEqualTo(VALUE_1);
     }
 
-    @Test(expected = IllegalArgumentException.class)
+    @Test
     public void insertOption_ALWAYSOVERRIDE_ALWAYSOVERRIDE() {
         MutableOptionsBundle mutOpts = MutableOptionsBundle.create();
 
         mutOpts.insertOption(OPTION_1, ALWAYS_OVERRIDE, VALUE_1);
-        // should throw an Error
         mutOpts.insertOption(OPTION_1, ALWAYS_OVERRIDE, VALUE_2);
+
+        assertThat(mutOpts.retrieveOption(OPTION_1)).isEqualTo(VALUE_2);
+        Config.OptionPriority highestPriority = Collections.min(mutOpts.getPriorities(OPTION_1));
+        assertThat(highestPriority).isEqualTo(ALWAYS_OVERRIDE);
+        assertThat(mutOpts.retrieveOptionWithPriority(OPTION_1, highestPriority))
+                .isEqualTo(VALUE_2);
     }
 
     @Test
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/compat/workaround/InvalidJpegDataParserTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/internal/compat/workaround/InvalidJpegDataParserTest.kt
index cdd2576..127b136 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/internal/compat/workaround/InvalidJpegDataParserTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/compat/workaround/InvalidJpegDataParserTest.kt
@@ -73,31 +73,34 @@
 @DoNotInstrument
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 class InvalidJpegDataParserTest(
+    private val brand: String,
     private val model: String,
     private val data: ByteArray,
     private val validDataLength: Int,
-    ) {
+) {
 
     companion object {
         @JvmStatic
-        @ParameterizedRobolectricTestRunner.Parameters(name = "model={0}, data={1}, length={2}")
+        @ParameterizedRobolectricTestRunner.Parameters(
+            name = "brand={0}, model={1}, data={2}, length={3}")
         fun data() = mutableListOf<Array<Any?>>().apply {
-            add(arrayOf("SM-A520F", problematicJpegByteArray, 18))
-            add(arrayOf("SM-A520F", problematicJpegByteArray2, 18))
-            add(arrayOf("SM-A520F", correctJpegByteArray1, 18))
-            add(arrayOf("SM-A520F", correctJpegByteArray2, 18))
-            add(arrayOf("SM-A520F", invalidVeryShortData, 2))
-            add(arrayOf("SM-A520F", invalidNoSosData, 28))
-            add(arrayOf("SM-A520F", invalidNoEoiData, 28))
-            add(arrayOf("fake-model", problematicJpegByteArray, 42))
-            add(arrayOf("fake-model", problematicJpegByteArray2, 64))
-            add(arrayOf("fake-model", correctJpegByteArray1, 28))
-            add(arrayOf("fake-model", correctJpegByteArray2, 18))
+            add(arrayOf("SAMSUNG", "SM-A520F", problematicJpegByteArray, 18))
+            add(arrayOf("SAMSUNG", "SM-A520F", problematicJpegByteArray2, 18))
+            add(arrayOf("SAMSUNG", "SM-A520F", correctJpegByteArray1, 18))
+            add(arrayOf("SAMSUNG", "SM-A520F", correctJpegByteArray2, 18))
+            add(arrayOf("SAMSUNG", "SM-A520F", invalidVeryShortData, 2))
+            add(arrayOf("SAMSUNG", "SM-A520F", invalidNoSosData, 28))
+            add(arrayOf("SAMSUNG", "SM-A520F", invalidNoEoiData, 28))
+            add(arrayOf("fake-brand", "fake-model", problematicJpegByteArray, 42))
+            add(arrayOf("fake-brand", "fake-model", problematicJpegByteArray2, 64))
+            add(arrayOf("fake-brand", "fake-model", correctJpegByteArray1, 28))
+            add(arrayOf("fake-brand", "fake-model", correctJpegByteArray2, 18))
         }
     }
 
     @Test
     fun canGetValidJpegDataLength() {
+        ReflectionHelpers.setStaticField(Build::class.java, "BRAND", brand)
         ReflectionHelpers.setStaticField(Build::class.java, "MODEL", model)
         assertThat(InvalidJpegDataParser().getValidDataLength(data)).isEqualTo(validDataLength)
     }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/StreamSharingTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/StreamSharingTest.kt
index 3b5c6ea..3743f65 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/StreamSharingTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/StreamSharingTest.kt
@@ -31,6 +31,7 @@
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY
 import androidx.camera.core.ImageProxy
+import androidx.camera.core.Preview
 import androidx.camera.core.SurfaceRequest
 import androidx.camera.core.impl.CameraCaptureCallback
 import androidx.camera.core.impl.CameraCaptureResult
@@ -41,6 +42,7 @@
 import androidx.camera.core.impl.UseCaseConfig
 import androidx.camera.core.impl.UseCaseConfigFactory
 import androidx.camera.core.impl.UseCaseConfigFactory.CaptureType
+import androidx.camera.core.impl.stabilization.StabilizationMode
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.directExecutor
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.camera.core.impl.utils.futures.Futures
@@ -55,6 +57,8 @@
 import androidx.camera.testing.impl.fakes.FakeUseCase
 import androidx.camera.testing.impl.fakes.FakeUseCaseConfig
 import androidx.camera.testing.impl.fakes.FakeUseCaseConfigFactory
+import androidx.camera.video.Recorder
+import androidx.camera.video.VideoCapture
 import com.google.common.truth.Truth.assertThat
 import com.google.common.util.concurrent.ListenableFuture
 import kotlinx.coroutines.CompletableDeferred
@@ -479,4 +483,34 @@
         assertThat(config.captureTypes[0]).isEqualTo(CaptureType.PREVIEW)
         assertThat(config.captureTypes[1]).isEqualTo(CaptureType.PREVIEW)
     }
+
+    @Test
+    fun getParentPreviewStabilizationMode_isPreviewChildMode() {
+        val preview = Preview.Builder().setPreviewStabilizationEnabled(true).build()
+        val videoCapture = VideoCapture.Builder(Recorder.Builder().build())
+            .setVideoStabilizationEnabled(false).build()
+
+        streamSharing =
+            StreamSharing(camera, setOf(preview, videoCapture), useCaseConfigFactory)
+        assertThat(
+            streamSharing.mergeConfigs(
+                camera.cameraInfoInternal, /*extendedConfig*/null, /*cameraDefaultConfig*/null
+            ).previewStabilizationMode
+        ).isEqualTo(StabilizationMode.ON)
+    }
+
+    @Test
+    fun getParentVideoStabilizationMode_isVideoCaptureChildMode() {
+        val preview = Preview.Builder().setPreviewStabilizationEnabled(false).build()
+        val videoCapture = VideoCapture.Builder(Recorder.Builder().build())
+            .setVideoStabilizationEnabled(true).build()
+
+        streamSharing =
+            StreamSharing(camera, setOf(preview, videoCapture), useCaseConfigFactory)
+        assertThat(
+            streamSharing.mergeConfigs(
+                camera.cameraInfoInternal, /*extendedConfig*/null, /*cameraDefaultConfig*/null
+            ).videoStabilizationMode
+        ).isEqualTo(StabilizationMode.ON)
+    }
 }
diff --git a/camera/camera-effects/src/androidTest/java/androidx/camera/effects/internal/SurfaceProcessorImplDeviceTest.kt b/camera/camera-effects/src/androidTest/java/androidx/camera/effects/internal/SurfaceProcessorImplDeviceTest.kt
new file mode 100644
index 0000000..e1c03b6
--- /dev/null
+++ b/camera/camera-effects/src/androidTest/java/androidx/camera/effects/internal/SurfaceProcessorImplDeviceTest.kt
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.effects.internal
+
+import android.graphics.Color
+import android.graphics.Matrix
+import android.graphics.Rect
+import android.graphics.SurfaceTexture
+import android.util.Size
+import android.view.Surface
+import androidx.camera.core.CameraEffect.PREVIEW
+import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
+import androidx.camera.core.SurfaceOutput
+import androidx.camera.core.SurfaceRequest
+import androidx.camera.core.SurfaceRequest.TransformationInfo
+import androidx.camera.core.impl.utils.TransformUtils.sizeToRect
+import androidx.camera.effects.Frame
+import androidx.camera.testing.fakes.FakeCamera
+import androidx.camera.testing.impl.TestImageUtil.getAverageDiff
+import androidx.core.util.Consumer
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumentation tests for [SurfaceProcessorImpl].
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 21)
+class SurfaceProcessorImplDeviceTest {
+
+    companion object {
+        private const val ROTATION_DEGREES = 90
+        private val TRANSFORM = Matrix().apply {
+            postRotate(90F)
+        }
+        private const val INPUT_COLOR = Color.GREEN
+        private const val OVERLAY_COLOR = Color.RED
+
+        // The timeout is set to 200ms to qualify for @SmallTest.
+        private const val TIMEOUT_MILLIS = 200L
+    }
+
+    private val size = Size(640, 480)
+    private val cropRect = sizeToRect(size)
+    private lateinit var surfaceRequest: SurfaceRequest
+    private lateinit var outputTexture: SurfaceTexture
+    private lateinit var outputSurface: Surface
+    private lateinit var outputTexture2: SurfaceTexture
+    private lateinit var outputSurface2: Surface
+    private lateinit var surfaceOutput: SurfaceOutput
+    private lateinit var surfaceOutput2: SurfaceOutput
+    private lateinit var processor: SurfaceProcessorImpl
+    private lateinit var transformationInfo: TransformationInfo
+
+    @Before
+    fun setUp() {
+        transformationInfo = TransformationInfo.of(
+            cropRect,
+            ROTATION_DEGREES,
+            Surface.ROTATION_90,
+            true,
+            TRANSFORM,
+            true
+        )
+        surfaceRequest = SurfaceRequest(size, FakeCamera()) {}
+        surfaceRequest.updateTransformationInfo(transformationInfo)
+        outputTexture = SurfaceTexture(0)
+        outputTexture.detachFromGLContext()
+        outputSurface = Surface(outputTexture)
+        outputTexture2 = SurfaceTexture(1)
+        outputTexture2.detachFromGLContext()
+        outputSurface2 = Surface(outputTexture2)
+        surfaceOutput = SurfaceOutputImpl(outputSurface, size)
+        surfaceOutput2 = SurfaceOutputImpl(outputSurface2, size)
+    }
+
+    @After
+    fun tearDown() {
+        outputTexture.release()
+        outputSurface.release()
+        outputTexture2.release()
+        outputSurface2.release()
+        if (::processor.isInitialized) {
+            processor.release()
+        }
+    }
+
+    @Test
+    fun onDrawListenerReturnsFalse_notDrawnToOutput() = runBlocking {
+        // Act: return false in the on draw listener.
+        val latch = fillFramesAndWaitForOutput(0, 1) {
+            it.setOnDrawListener { false }
+        }
+        // Assert: output is not drawn.
+        assertThat(latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isFalse()
+    }
+
+    @Test
+    fun onDrawListener_receivesTransformationInfo() = runBlocking {
+        // Arrange.
+        var frameReceived: Frame? = null
+        // Act: fill frames and wait draw frame listener.
+        val latch = fillFramesAndWaitForOutput(0, 1) { processor ->
+            processor.setOnDrawListener { frame ->
+                frameReceived = frame
+                true
+            }
+        }
+        // Assert: draw frame listener receives correct transformation info.
+        assertThat(latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+        assertThat(frameReceived!!.size).isEqualTo(size)
+        assertThat(frameReceived!!.cropRect).isEqualTo(transformationInfo.cropRect)
+        assertThat(frameReceived!!.getMirroring()).isEqualTo(transformationInfo.mirroring)
+        assertThat(frameReceived!!.sensorToBufferTransform)
+            .isEqualTo(transformationInfo.sensorToBufferTransform)
+        assertThat(frameReceived!!.rotationDegrees).isEqualTo(ROTATION_DEGREES)
+    }
+
+    @Test
+    fun canvasInvalidated_overlayDrawnToOutput(): Unit = runBlocking {
+        val latch = fillFramesAndWaitForOutput(0, 1) { processor ->
+            processor.setOnDrawListener { frame ->
+                // Act: invalidate overlay canvas and draw color.
+                frame.invalidateOverlayCanvas().drawColor(OVERLAY_COLOR)
+                true
+            }
+        }
+        // Assert: output receives frame with overlay color.
+        assertThat(latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+        assertOutputColor(OVERLAY_COLOR)
+    }
+
+    @Test
+    fun canvasNotInvalidated_overlayNotDrawnToOutput() = runBlocking {
+        val latch = fillFramesAndWaitForOutput(0, 1) { processor ->
+            processor.setOnDrawListener { frame ->
+                // Act: draw color on overlay canvas without invalidating.
+                frame.overlayCanvas.drawColor(OVERLAY_COLOR)
+                true
+            }
+        }
+        // Assert: output receives frame with input color
+        assertThat(latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+        assertOutputColor(INPUT_COLOR)
+    }
+
+    @Test
+    fun zeroQueueDepth_inputDrawnToOutput() = runBlocking {
+        // Assert: output receives frame when queue depth == 0.
+        val latch = fillFramesAndWaitForOutput(0, 1)
+        assertThat(latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+    }
+
+    @Test
+    fun nonZeroQueueDepth_inputNotDrawnToOutputBeforeFilledUp() = runBlocking {
+        // Assert: output does not receive frame when frame count = queue depth.
+        val latch = fillFramesAndWaitForOutput(3, 3)
+        assertThat(latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isFalse()
+    }
+
+    @Test
+    fun nonZeroQueueDepth_inputDrawnToOutputAfterFilledUp() = runBlocking {
+        // Assert: output receives frame when frame count > queue depth.
+        val latch = fillFramesAndWaitForOutput(3, 4)
+        assertThat(latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+    }
+
+    @Test
+    fun replaceOutputSurface_noFrameFromPreviousCycle() = runBlocking {
+        // Arrange: setup processor with buffer depth == 1 and fill it full.
+        processor = SurfaceProcessorImpl(1)
+        withContext(processor.glExecutor.asCoroutineDispatcher()) {
+            processor.onInputSurface(surfaceRequest)
+            processor.onOutputSurface(surfaceOutput)
+        }
+        val inputSurface = surfaceRequest.deferrableSurface.surface.get()
+        drawSurface(inputSurface)
+
+        // Act: replace output surface so the cached frame is no longer valid. The cached frame
+        // should be marked empty and not blocking the pipeline.
+        val countDownLatch = getTextureUpdateLatch(outputTexture2)
+        withContext(processor.glExecutor.asCoroutineDispatcher()) {
+            processor.onOutputSurface(surfaceOutput2)
+        }
+
+        // Assert: draw the input surface twice and the output surface should receive a frame. It
+        // confirms that there is no frame from the previous cycle blocking the pipeline.
+        drawSurface(inputSurface)
+        drawSurface(inputSurface)
+        assertThat(countDownLatch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+    }
+
+    /**
+     * Renders the input surface to a bitmap and asserts that the color of the bitmap.
+     */
+    private suspend fun assertOutputColor(color: Int) {
+        val matrix = FloatArray(16)
+        android.opengl.Matrix.setIdentityM(matrix, 0)
+        withContext(processor.glExecutor.asCoroutineDispatcher()) {
+            val bitmap = processor.glRendererForTesting
+                .renderInputToBitmap(size.width, size.height, matrix)
+            assertThat(
+                getAverageDiff(
+                    bitmap,
+                    Rect(0, 0, size.width, size.height),
+                    color
+                )
+            ).isEqualTo(0)
+        }
+    }
+
+    /**
+     * Creates a processor and draws frames to the input surface.
+     *
+     * @param queueDepth The queue depth of the processor.
+     * @param frameCount The number of frames to draw.
+     * @return True if the output surface receives a frame.
+     */
+    private suspend fun fillFramesAndWaitForOutput(
+        queueDepth: Int,
+        frameCount: Int,
+        configureProcessor: (SurfaceProcessorImpl) -> Unit = {},
+    ): CountDownLatch {
+        // Arrange: Create a processor.
+        processor = SurfaceProcessorImpl(queueDepth)
+        configureProcessor(processor)
+        withContext(processor.glExecutor.asCoroutineDispatcher()) {
+            processor.onInputSurface(surfaceRequest)
+            processor.onOutputSurface(surfaceOutput)
+        }
+        val countDownLatch = getTextureUpdateLatch(outputTexture)
+
+        // Act: Draw frames to the input surface.
+        val inputSurface = surfaceRequest.deferrableSurface.surface.get()
+
+        repeat(frameCount) {
+            drawSurface(inputSurface)
+        }
+
+        return countDownLatch
+    }
+
+    /**
+     * Draws a frame to the surface and block the thread until the gl thread finishes processing.
+     */
+    private suspend fun drawSurface(surface: Surface) {
+        val canvas = surface.lockCanvas(null)
+        canvas.drawColor(INPUT_COLOR)
+        surface.unlockCanvasAndPost(canvas)
+        // Drain the GL thread to ensure the processor caches or draws the frame. Otherwise, the
+        // input SurfaceTexture's onSurfaceAvailable callback may only get called once for
+        // multiple drawings.
+        withContext(processor.glExecutor.asCoroutineDispatcher()) {
+        }
+    }
+
+    private fun getTextureUpdateLatch(surfaceTexture: SurfaceTexture): CountDownLatch {
+        val countDownLatch = CountDownLatch(1)
+        surfaceTexture.setOnFrameAvailableListener {
+            countDownLatch.countDown()
+        }
+        return countDownLatch
+    }
+
+    private class SurfaceOutputImpl(private val surface: Surface, val surfaceSize: Size) :
+        SurfaceOutput {
+
+        override fun close() {
+        }
+
+        override fun getSurface(
+            executor: Executor,
+            listener: Consumer<SurfaceOutput.Event>
+        ): Surface {
+            return surface
+        }
+
+        override fun getTargets(): Int {
+            return PREVIEW or VIDEO_CAPTURE
+        }
+
+        override fun getSize(): Size {
+            return surfaceSize
+        }
+
+        override fun updateTransformMatrix(updated: FloatArray, original: FloatArray) {
+        }
+    }
+}
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/Frame.java b/camera/camera-effects/src/main/java/androidx/camera/effects/Frame.java
new file mode 100644
index 0000000..ee6b8a9
--- /dev/null
+++ b/camera/camera-effects/src/main/java/androidx/camera/effects/Frame.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.effects;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraCharacteristics;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.camera.core.ImageInfo;
+import androidx.camera.core.SurfaceRequest;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Represents a frame that will be rendered next.
+ *
+ * <p>This class can be used to overlay graphics or data on camera output. It contains
+ * information for drawing an overlay, including sensor-to-buffer transform, size, crop rect,
+ * rotation, mirroring, and timestamp. It also provides a {@link Canvas} for the drawing.
+ *
+ * TODO(b/297509601): Make it public API in 1.4.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RequiresApi(21)
+@AutoValue
+public abstract class Frame {
+
+    private boolean mIsOverlayDirty = false;
+
+    /**
+     * Internal API to create a frame.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @NonNull
+    public static Frame of(
+            @NonNull Canvas overlayCanvas,
+            long timestampNs,
+            @NonNull Size size,
+            @NonNull SurfaceRequest.TransformationInfo transformationInfo) {
+        return new AutoValue_Frame(transformationInfo.getSensorToBufferTransform(), size,
+                transformationInfo.getCropRect(), transformationInfo.getRotationDegrees(),
+                transformationInfo.getMirroring(), timestampNs, overlayCanvas);
+    }
+
+    /**
+     * Returns the sensor to image buffer transform matrix.
+     *
+     * <p>The value is a mapping from sensor coordinates to buffer coordinates, which is,
+     * from the rect of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE} to the
+     * rect defined by {@code (0, 0, #getSize()#getWidth(), #getSize()#getHeight())}.
+     *
+     * <p>The value can be set on the {@link Canvas} using {@link Canvas#setMatrix} API. This
+     * transforms the {@link Canvas} to the sensor coordinate system.
+     *
+     * @see SurfaceRequest.TransformationInfo#getSensorToBufferTransform()
+     */
+    @NonNull
+    public abstract Matrix getSensorToBufferTransform();
+
+    /**
+     * Returns the resolution of the frame.
+     *
+     * @see SurfaceRequest#getResolution()
+     */
+    @NonNull
+    public abstract Size getSize();
+
+    /**
+     * Returns the crop rect rectangle.
+     *
+     * <p>The value represents how the frame will be cropped by the CameraX pipeline. The crop
+     * rectangle specifies the region of valid pixels in the frame, using coordinates from (0, 0)
+     * to the (width, height) of {@link #getSize()}. Only the overlay drawn within the bound of
+     * the crop rect will be visible to the end users.
+     *
+     * <p>The crop rect is applied before the rotating and mirroring. The order of the operations
+     * is as follows: 1) cropping, 2) rotating and 3) mirroring.
+     *
+     * @see SurfaceRequest.TransformationInfo#getCropRect()
+     */
+    @NonNull
+    public abstract Rect getCropRect();
+
+    /**
+     * Returns the rotation degrees of the frame.
+     *
+     * <p>This is a clockwise rotation in degrees that needs to be applied to the frame. The
+     * rotation will be determined by {@link CameraCharacteristics} and UseCase configuration.
+     * The app must draw the overlay according to the rotation degrees to ensure it is
+     * displayed correctly to the end users.
+     *
+     * <p>The rotation is applied after the cropping but before the mirroring. The order of the
+     * operations is as follows: 1) cropping, 2) rotating and 3) mirroring.
+     *
+     * @see SurfaceRequest.TransformationInfo#getRotationDegrees()
+     */
+    public abstract int getRotationDegrees();
+
+    /**
+     * Returns whether the buffer will be mirrored.
+     *
+     * <p>This flag indicates whether the buffer will be mirrored by the pipeline vertically. For
+     * example, for front camera preview, the buffer is usually mirrored before displayed to end
+     * users.
+     *
+     * <p>The mirroring is applied after the cropping and the rotating. The order of the
+     * operations is as follows: 1) cropping, 2) rotating and 3) mirroring.
+     *
+     * @see SurfaceRequest.TransformationInfo#getMirroring()
+     */
+    public abstract boolean getMirroring();
+
+    /**
+     * Returns the timestamp of the frame in nanoseconds.
+     *
+     * @see SurfaceTexture#getTimestamp()
+     * @see ImageInfo#getTimestamp()
+     */
+    public abstract long getTimestampNs();
+
+    /**
+     * Invalidates and returns the overlay canvas.
+     *
+     * <p>Call this method to get the {@link Canvas} for drawing an overlay on top of the frame.
+     * The {@link Canvas} is backed by a {@link Bitmap} with the sizes equals {@link #getSize()} and
+     * the format equals {@link Bitmap.Config#ARGB_8888}. To draw object in camera sensor
+     * coordinates, apply {@link #getSensorToBufferTransform()} via
+     * {@link Canvas#setMatrix(Matrix)} before drawing.
+     *
+     * <p>Only call this method if the caller needs to draw overlay on the frame. Calling this
+     * method will upload the {@link Bitmap} to GPU for blending.
+     */
+    @NonNull
+    public Canvas invalidateOverlayCanvas() {
+        mIsOverlayDirty = true;
+        return getOverlayCanvas();
+    }
+
+    /**
+     * Internal API to check whether the overlay canvas is dirty.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public boolean isOverlayDirty() {
+        return mIsOverlayDirty;
+    }
+
+
+    /**
+     * Internal API to get the overlay canvas without invalidating it.
+     */
+    @NonNull
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public abstract Canvas getOverlayCanvas();
+}
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/internal/SurfaceProcessorImpl.java b/camera/camera-effects/src/main/java/androidx/camera/effects/internal/SurfaceProcessorImpl.java
new file mode 100644
index 0000000..2c8ec15
--- /dev/null
+++ b/camera/camera-effects/src/main/java/androidx/camera/effects/internal/SurfaceProcessorImpl.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.camera.effects.internal;
+
+import static androidx.core.util.Preconditions.checkArgument;
+import static androidx.core.util.Preconditions.checkState;
+
+import static java.util.Objects.requireNonNull;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.SurfaceTexture;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
+import androidx.arch.core.util.Function;
+import androidx.camera.core.SurfaceOutput;
+import androidx.camera.core.SurfaceProcessor;
+import androidx.camera.core.SurfaceRequest;
+import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.camera.effects.Frame;
+import androidx.camera.effects.opengl.GlRenderer;
+import androidx.core.util.Pair;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Implementation of {@link SurfaceProcessor} that applies an overlay to the input surface.
+ *
+ * <p>This implementation only expects one input surface and one output surface.
+ */
+@RequiresApi(21)
+public class SurfaceProcessorImpl implements SurfaceProcessor,
+        SurfaceTexture.OnFrameAvailableListener {
+
+    private static final String GL_THREAD_NAME = "OverlayGlThread";
+
+    // GL thread and handler.
+    private final HandlerThread mGlThread;
+    private final Handler mGlHandler;
+    private final Executor mGlExecutor;
+
+    // GL renderer.
+    private final GlRenderer mGlRenderer = new GlRenderer();
+    private final int mQueueDepth;
+
+    // Transform matrices.
+    private final float[] mSurfaceTransform = new float[16];
+    private final float[] mTextureTransform = new float[16];
+
+    // Surfaces and buffers.
+    @Nullable
+    private Size mInputSize = null;
+    @Nullable
+    private TextureFrameBuffer mBuffer = null;
+    @Nullable
+    private Bitmap mOverlayBitmap;
+    @Nullable
+    private Canvas mOverlayCanvas;
+    @Nullable
+    private Pair<SurfaceOutput, Surface> mOutputSurfacePair = null;
+    @Nullable
+    private SurfaceRequest.TransformationInfo mTransformationInfo = null;
+    @Nullable
+    private Function<Frame, Boolean> mOnDrawListener;
+
+    private boolean mIsReleased = false;
+
+    public SurfaceProcessorImpl(int queueDepth) {
+        mGlThread = new HandlerThread(GL_THREAD_NAME);
+        mGlThread.start();
+        mGlHandler = new Handler(mGlThread.getLooper());
+        mGlExecutor = CameraXExecutors.newHandlerExecutor(mGlHandler);
+        mQueueDepth = queueDepth;
+        runOnGlThread(mGlRenderer::init);
+    }
+
+    @Override
+    public void onInputSurface(@NonNull SurfaceRequest surfaceRequest) {
+        checkGlThread();
+        if (mIsReleased) {
+            surfaceRequest.willNotProvideSurface();
+            return;
+        }
+
+        // Configure input surface and listen for frame updates.
+        SurfaceTexture surfaceTexture = new SurfaceTexture(mGlRenderer.getInputTextureId());
+        surfaceTexture.setDefaultBufferSize(surfaceRequest.getResolution().getWidth(),
+                surfaceRequest.getResolution().getHeight());
+        Surface surface = new Surface(surfaceTexture);
+        surfaceRequest.provideSurface(surface, mGlExecutor, result -> {
+            // TODO(b/297509601): maybe release the buffer to free up memory.
+            surfaceTexture.setOnFrameAvailableListener(null);
+            surfaceTexture.release();
+            surface.release();
+        });
+        surfaceTexture.setOnFrameAvailableListener(this, mGlHandler);
+
+        // Listen for transformation updates.
+        mTransformationInfo = null;
+        surfaceRequest.setTransformationInfoListener(mGlExecutor, transformationInfo ->
+                mTransformationInfo = transformationInfo);
+
+        // Configure buffers based on the input size.
+        createBufferAndOverlay(surfaceRequest.getResolution());
+    }
+
+    @Override
+    public void onOutputSurface(@NonNull SurfaceOutput surfaceOutput) {
+        checkGlThread();
+        if (mIsReleased) {
+            surfaceOutput.close();
+            return;
+        }
+
+        Surface surface = surfaceOutput.getSurface(mGlExecutor, result -> {
+            surfaceOutput.close();
+            // When the output surface is closed, unregister if it's the same Surface.
+            if (mOutputSurfacePair != null && mOutputSurfacePair.first == surfaceOutput) {
+                mGlRenderer.unregisterOutputSurface(requireNonNull(mOutputSurfacePair.second));
+                mOutputSurfacePair = null;
+            }
+        });
+
+        // Only one output Surface is allowed. Unregister the existing Surface before registering
+        // the new one.
+        if (mOutputSurfacePair != null) {
+            mGlRenderer.unregisterOutputSurface(requireNonNull(mOutputSurfacePair.second));
+        }
+        mGlRenderer.registerOutputSurface(surface);
+        mOutputSurfacePair = Pair.create(surfaceOutput, surface);
+    }
+
+    @Override
+    public void onFrameAvailable(@NonNull SurfaceTexture surfaceTexture) {
+        checkGlThread();
+        if (mIsReleased) {
+            return;
+        }
+        if (mOutputSurfacePair == null) {
+            // Output surface not ready. Skip.
+            return;
+        }
+
+        // Get the GL transform.
+        surfaceTexture.updateTexImage();
+        surfaceTexture.getTransformMatrix(mTextureTransform);
+        Surface surface = requireNonNull(mOutputSurfacePair.second);
+        SurfaceOutput surfaceOutput = requireNonNull(mOutputSurfacePair.first);
+        surfaceOutput.updateTransformMatrix(mSurfaceTransform, mTextureTransform);
+
+        if (requireNonNull(mBuffer).getLength() == 0) {
+            // There is no buffer. Render directly to the output surface.
+            if (drawOverlay(surfaceTexture.getTimestamp())) {
+                mGlRenderer.renderInputToSurface(
+                        surfaceTexture.getTimestamp(),
+                        mSurfaceTransform,
+                        requireNonNull(surface));
+            }
+        } else {
+            // Cache the frame to the buffer.
+            TextureFrame frameToFill = mBuffer.getFrameToFill();
+            if (!frameToFill.isEmpty()) {
+                // The buffer is full. Release the oldest frame and free up a slot.
+                drawFrameAndMarkEmpty(frameToFill);
+            }
+            mGlRenderer.renderInputToQueueTexture(frameToFill.getTextureId());
+            frameToFill.markFilled(surfaceTexture.getTimestamp(), mSurfaceTransform, surface);
+        }
+    }
+
+    /**
+     * Releases the processor and all the resources it holds.
+     *
+     * <p>Once released, the processor can no longer be used.
+     */
+    public void release() {
+        runOnGlThread(() -> {
+            if (!mIsReleased) {
+                if (mOutputSurfacePair != null) {
+                    requireNonNull(mOutputSurfacePair.first).close();
+                    mOutputSurfacePair = null;
+                }
+                mGlRenderer.release();
+                mGlThread.quitSafely();
+                mBuffer = null;
+                mOverlayBitmap = null;
+                mOverlayCanvas = null;
+                mInputSize = null;
+                mIsReleased = true;
+            }
+        });
+    }
+
+    /**
+     * Gets the {@link Executor} used by OpenGL.
+     */
+    @NonNull
+    public Executor getGlExecutor() {
+        return mGlExecutor;
+    }
+
+    /**
+     * Sets the listener that listens to frame updates and draws overlay.
+     *
+     * <p>CameraX invokes this {@link Function} on the GL thread each time a frame is drawn. The
+     * caller can use implement the {@link Function} to draw overlay on the frame.
+     *
+     * <p>The {@link Function} accepts a {@link Frame} object which provides information on how to
+     * draw the overlay. The return value of the {@link Function} indicates whether the frame
+     * should be drawn. If false, the frame will be dropped.
+     */
+    public void setOnDrawListener(@Nullable Function<Frame, Boolean> onDrawListener) {
+        runOnGlThread(() -> mOnDrawListener = onDrawListener);
+    }
+
+    // *** Private methods ***
+
+    private void runOnGlThread(@NonNull Runnable runnable) {
+        if (isGlThread()) {
+            runnable.run();
+        } else {
+            mGlHandler.post(runnable);
+        }
+    }
+
+    private void createBufferAndOverlay(@NonNull Size inputSize) {
+        checkGlThread();
+        if (inputSize.equals(mInputSize)) {
+            // Input size unchanged. No need to reallocate buffers.
+            return;
+        }
+        mInputSize = inputSize;
+
+        // Create a buffer of textures with the same size as the input.
+        int[] textureIds = mGlRenderer.createBufferTextureIds(mQueueDepth, mInputSize);
+        mBuffer = new TextureFrameBuffer(textureIds);
+
+        // Create the overlay Bitmap with the same size as the input.
+        mOverlayBitmap = Bitmap.createBitmap(inputSize.getWidth(), inputSize.getHeight(),
+                Bitmap.Config.ARGB_8888);
+        mOverlayCanvas = new Canvas(mOverlayBitmap);
+        mOverlayCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
+        mGlRenderer.uploadOverlay(mOverlayBitmap);
+    }
+
+    private void drawFrameAndMarkEmpty(@NonNull TextureFrame frame) {
+        checkGlThread();
+        checkArgument(!frame.isEmpty());
+        if (mOutputSurfacePair != null && mOutputSurfacePair.second == frame.getSurface()) {
+            // Only draw if frame is associated with the current output surface.
+            if (drawOverlay(frame.getTimestampNs())) {
+                mGlRenderer.renderQueueTextureToSurface(
+                        frame.getTextureId(),
+                        frame.getTimestampNs(),
+                        frame.getTransform(),
+                        frame.getSurface());
+            }
+        }
+        frame.markEmpty();
+    }
+
+    /**
+     * Requests the app to draw overlay.
+     *
+     * <p>This method invokes app's callback to draw overlay and upload the result to GPU.
+     *
+     * <p>The caller should only render the frame if this method returns true.
+     */
+    @SuppressWarnings("unused")
+    private boolean drawOverlay(long timestampNs) {
+        checkGlThread();
+        if (mTransformationInfo == null || mOnDrawListener == null) {
+            return true;
+        }
+        Frame frame = Frame.of(
+                requireNonNull(mOverlayCanvas),
+                timestampNs,
+                requireNonNull(mInputSize),
+                mTransformationInfo);
+        if (!mOnDrawListener.apply(frame)) {
+            // The caller wants to drop the frame.
+            return false;
+        }
+        if (frame.isOverlayDirty()) {
+            mGlRenderer.uploadOverlay(requireNonNull(mOverlayBitmap));
+        }
+        return true;
+    }
+
+    private void checkGlThread() {
+        checkState(isGlThread(), "Must be called on GL thread");
+    }
+
+    private boolean isGlThread() {
+        return Thread.currentThread() == mGlThread;
+    }
+
+    @VisibleForTesting
+    GlRenderer getGlRendererForTesting() {
+        return mGlRenderer;
+    }
+}
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/compat/workaround/OnEnableDisableSessionDurationCheckTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/compat/workaround/OnEnableDisableSessionDurationCheckTest.kt
index abd3bda..9bc767e 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/compat/workaround/OnEnableDisableSessionDurationCheckTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/compat/workaround/OnEnableDisableSessionDurationCheckTest.kt
@@ -17,6 +17,7 @@
 package androidx.camera.extensions.internal.compat.workaround
 
 import androidx.camera.extensions.internal.compat.workaround.OnEnableDisableSessionDurationCheck.MIN_DURATION_FOR_ENABLE_DISABLE_SESSION
+import androidx.camera.testing.impl.AndroidUtil
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
@@ -24,6 +25,8 @@
 import kotlin.system.measureTimeMillis
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.runBlocking
+import org.junit.Assume.assumeFalse
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -35,6 +38,11 @@
         const val TOLERANCE = 60L
     }
 
+    @Before
+    fun setUp() {
+        assumeFalse(AndroidUtil.isEmulatorAndAPI21())
+    }
+
     @Test
     fun enabled_ensureMinimalDuration() = runBlocking {
         // Arrange
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
index b9a149e..508f54d8 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
@@ -314,13 +314,25 @@
     @Override
     public Size[] getSupportedYuvAnalysisResolutions() {
         ImageAnalysisAvailability imageAnalysisAvailability = new ImageAnalysisAvailability();
-        if (imageAnalysisAvailability.isUnavailable(mCameraId, mMode)) {
+        boolean hasPreviewProcessor = mPreviewExtenderImpl.getProcessorType()
+                == PreviewExtenderImpl.ProcessorType.PROCESSOR_TYPE_IMAGE_PROCESSOR;
+        boolean hasImageCaptureProcessor = mImageCaptureExtenderImpl.getCaptureProcessor() != null;
+        if (!imageAnalysisAvailability.isAvailable(mCameraId, getHardwareLevel(), mMode,
+                hasPreviewProcessor, hasImageCaptureProcessor)) {
             return new Size[0];
         }
         Preconditions.checkNotNull(mCameraInfo, "VendorExtender#init() must be called first");
         return getOutputSizes(ImageFormat.YUV_420_888);
     }
 
+    private int getHardwareLevel() {
+        Integer hardwareLevel =
+                mCameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+
+        return hardwareLevel != null ? hardwareLevel :
+                CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
+    }
+
     @NonNull
     private List<CaptureRequest.Key> getSupportedParameterKeys(Context context) {
         if (ExtensionVersion.isMinimumCompatibleVersion(Version.VERSION_1_3)) {
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java
index fc7a6a0..74a8f42 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java
@@ -56,6 +56,10 @@
             quirks.add(new ImageAnalysisUnavailableQuirk());
         }
 
+        if (ExtraSupportedSurfaceCombinationsQuirk.load()) {
+            quirks.add(new ExtraSupportedSurfaceCombinationsQuirk());
+        }
+
         return quirks;
     }
 }
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.java
new file mode 100644
index 0000000..023d5b8
--- /dev/null
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.extensions.internal.compat.quirk;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.impl.Quirk;
+import androidx.camera.extensions.internal.compat.workaround.ImageAnalysisAvailability;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * <p>QuirkSummary
+ *     Bug Id: b/194149215
+ *     Description: Quirk required to include extra supported surface combinations which are
+ *                  additional to the guaranteed supported configurations. An example is the
+ *                  Samsung A51's LIMITED-level camera device can support additional YUV/640x480
+ *                  + PRIV/PREVIEW + YUV/MAXIMUM and YUV/640x480 + YUV/PREVIEW + YUV/MAXIMUM
+ *                  configurations.
+ *     Device(s): Some Samsung devices
+ *     @see ImageAnalysisAvailability
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class ExtraSupportedSurfaceCombinationsQuirk implements Quirk {
+
+    /**
+     * All devices in the list can support YUV/640x480 + PRIV/PREVIEW + YUV/MAXIMUM and
+     * YUV/640x480 + YUV/PREVIEW + YUV/MAXIMUM configurations.
+     */
+    private static final Set<String> SUPPORT_EXTRA_FULL_CONFIGURATIONS_SAMSUNG_MODELS =
+            new HashSet<>(Arrays.asList(
+                    "SM-A515F", // Galaxy A51
+                    "SM-A515U", // Galaxy A51
+                    "SM-A515U1", // Galaxy A51
+                    "SM-A515W", // Galaxy A51
+                    "SM-S515DL", // Galaxy A51
+                    "SC-54A", // Galaxy A51 5G
+                    "SCG07", // Galaxy A51 5G
+                    "SM-A5160", // Galaxy A51 5G
+                    "SM-A516B", // Galaxy A51 5G
+                    "SM-A516N", // Galaxy A51 5G
+                    "SM-A516U", // Galaxy A51 5G
+                    "SM-A516U1", // Galaxy A51 5G
+                    "SM-A516V", // Galaxy A51 5G
+                    "SM-A715F", // Galaxy A71
+                    "SM-A715W", // Galaxy A71
+                    "SM-A7160", // Galaxy A71 5G
+                    "SM-A716B", // Galaxy A71 5G
+                    "SM-A716U", // Galaxy A71 5G
+                    "SM-A716U1", // Galaxy A71 5G
+                    "SM-A716V", // Galaxy A71 5G
+                    "SM-A8050", // Galaxy A80
+                    "SM-A805F", // Galaxy A80
+                    "SM-A805N", // Galaxy A80
+                    "SCV44", // Galaxy Fold
+                    "SM-F9000", // Galaxy Fold
+                    "SM-F900F", // Galaxy Fold
+                    "SM-F900U", // Galaxy Fold
+                    "SM-F900U1", // Galaxy Fold
+                    "SM-F900W", // Galaxy Fold
+                    "SM-F907B", // Galaxy Fold 5G
+                    "SM-F907N", // Galaxy Fold 5G
+                    "SM-N970F", // Galaxy Note10
+                    "SM-N9700", // Galaxy Note10
+                    "SM-N970U", // Galaxy Note10
+                    "SM-N970U1", // Galaxy Note10
+                    "SM-N970W", // Galaxy Note10
+                    "SM-N971N", // Galaxy Note10 5G
+                    "SM-N770F", // Galaxy Note10 Lite
+                    "SC-01M", // Galaxy Note10+
+                    "SCV45", // Galaxy Note10+
+                    "SM-N9750", // Galaxy Note10+
+                    "SM-N975C", // Galaxy Note10+
+                    "SM-N975U", // Galaxy Note10+
+                    "SM-N975U1", // Galaxy Note10+
+                    "SM-N975W", // Galaxy Note10+
+                    "SM-N975F", // Galaxy Note10+
+                    "SM-N976B", // Galaxy Note10+ 5G
+                    "SM-N976N", // Galaxy Note10+ 5G
+                    "SM-N9760", // Galaxy Note10+ 5G
+                    "SM-N976Q", // Galaxy Note10+ 5G
+                    "SM-N976V", // Galaxy Note10+ 5G
+                    "SM-N976U", // Galaxy Note10+ 5G
+                    "SM-N9810", // Galaxy Note20 5G
+                    "SM-N981N", // Galaxy Note20 5G
+                    "SM-N981U", // Galaxy Note20 5G
+                    "SM-N981U1", // Galaxy Note20 5G
+                    "SM-N981W", // Galaxy Note20 5G
+                    "SM-N981B", // Galaxy Note20 5G
+                    "SC-53A", // Galaxy Note20 Ultra 5G
+                    "SCG06", // Galaxy Note20 Ultra 5G
+                    "SM-N9860", // Galaxy Note20 Ultra 5G
+                    "SM-N986N", // Galaxy Note20 Ultra 5G
+                    "SM-N986U", // Galaxy Note20 Ultra 5G
+                    "SM-N986U1", // Galaxy Note20 Ultra 5G
+                    "SM-N986W", // Galaxy Note20 Ultra 5G
+                    "SM-N986B", // Galaxy Note20 Ultra 5G
+                    "SC-03L", // Galaxy S10
+                    "SCV41", // Galaxy S10
+                    "SM-G973F", // Galaxy S10
+                    "SM-G973N", // Galaxy S10
+                    "SM-G9730", // Galaxy S10
+                    "SM-G9738", // Galaxy S10
+                    "SM-G973C", // Galaxy S10
+                    "SM-G973U", // Galaxy S10
+                    "SM-G973U1", // Galaxy S10
+                    "SM-G973W", // Galaxy S10
+                    "SM-G977B", // Galaxy S10 5G
+                    "SM-G977N", // Galaxy S10 5G
+                    "SM-G977P", // Galaxy S10 5G
+                    "SM-G977T", // Galaxy S10 5G
+                    "SM-G977U", // Galaxy S10 5G
+                    "SM-G770F", // Galaxy S10 Lite
+                    "SM-G770U1", // Galaxy S10 Lite
+                    "SC-04L", // Galaxy S10+
+                    "SCV42", // Galaxy S10+
+                    "SM-G975F", // Galaxy S10+
+                    "SM-G975N", // Galaxy S10+
+                    "SM-G9750", // Galaxy S10+
+                    "SM-G9758", // Galaxy S10+
+                    "SM-G975U", // Galaxy S10+
+                    "SM-G975U1", // Galaxy S10+
+                    "SM-G975W", // Galaxy S10+
+                    "SC-05L", // Galaxy S10+ Olympic Games Edition
+                    "SM-G970F", // Galaxy S10e
+                    "SM-G970N", // Galaxy S10e
+                    "SM-G9700", // Galaxy S10e
+                    "SM-G9708", // Galaxy S10e
+                    "SM-G970U", // Galaxy S10e
+                    "SM-G970U1", // Galaxy S10e
+                    "SM-G970W", // Galaxy S10e
+                    "SC-51A", // Galaxy S20 5G
+                    "SC51Aa", // Galaxy S20 5G
+                    "SCG01", // Galaxy S20 5G
+                    "SM-G9810", // Galaxy S20 5G
+                    "SM-G981N", // Galaxy S20 5G
+                    "SM-G981U", // Galaxy S20 5G
+                    "SM-G981U1", // Galaxy S20 5G
+                    "SM-G981V", // Galaxy S20 5G
+                    "SM-G981W", // Galaxy S20 5G
+                    "SM-G981B", // Galaxy S20 5G
+                    "SCG03", // Galaxy S20 Ultra 5G
+                    "SM-G9880", // Galaxy S20 Ultra 5G
+                    "SM-G988N", // Galaxy S20 Ultra 5G
+                    "SM-G988Q", // Galaxy S20 Ultra 5G
+                    "SM-G988U", // Galaxy S20 Ultra 5G
+                    "SM-G988U1", // Galaxy S20 Ultra 5G
+                    "SM-G988W", // Galaxy S20 Ultra 5G
+                    "SM-G988B", // Galaxy S20 Ultra 5G
+                    "SC-52A", // Galaxy S20+ 5G
+                    "SCG02", // Galaxy S20+ 5G
+                    "SM-G9860", // Galaxy S20+ 5G
+                    "SM-G986N", // Galaxy S20+ 5G
+                    "SM-G986U", // Galaxy S20+ 5G
+                    "SM-G986U1", // Galaxy S20+ 5G
+                    "SM-G986W", // Galaxy S20+ 5G
+                    "SM-G986B", // Galaxy S20+ 5G
+                    "SCV47", // Galaxy Z Flip
+                    "SM-F7000", // Galaxy Z Flip
+                    "SM-F700F", // Galaxy Z Flip
+                    "SM-F700N", // Galaxy Z Flip
+                    "SM-F700U", // Galaxy Z Flip
+                    "SM-F700U1", // Galaxy Z Flip
+                    "SM-F700W", // Galaxy Z Flip
+                    "SCG04", // Galaxy Z Flip 5G
+                    "SM-F7070", // Galaxy Z Flip 5G
+                    "SM-F707B", // Galaxy Z Flip 5G
+                    "SM-F707N", // Galaxy Z Flip 5G
+                    "SM-F707U", // Galaxy Z Flip 5G
+                    "SM-F707U1", // Galaxy Z Flip 5G
+                    "SM-F707W", // Galaxy Z Flip 5G
+                    "SM-F9160", // Galaxy Z Fold2 5G
+                    "SM-F916B", // Galaxy Z Fold2 5G
+                    "SM-F916N", // Galaxy Z Fold2 5G
+                    "SM-F916Q", // Galaxy Z Fold2 5G
+                    "SM-F916U", // Galaxy Z Fold2 5G
+                    "SM-F916U1", // Galaxy Z Fold2 5G
+                    "SM-F916W"// Galaxy Z Fold2 5G
+            ));
+
+    /**
+     * Currently, once the quirk is loaded, it means that the device can support YUV/640x480 +
+     * PRIV/PREVIEW + YUV/MAXIMUM and YUV/640x480 + YUV/PREVIEW + YUV/MAXIMUM configurations even
+     * if it is LIMITED level device.
+     */
+    static boolean load() {
+        return supportExtraFullConfigurationsSamsungDevice();
+    }
+
+    private static boolean supportExtraFullConfigurationsSamsungDevice() {
+        if (!"samsung".equalsIgnoreCase(Build.BRAND)) {
+            return false;
+        }
+
+        String capitalModelName = Build.MODEL.toUpperCase(Locale.US);
+
+        return SUPPORT_EXTRA_FULL_CONFIGURATIONS_SAMSUNG_MODELS.contains(capitalModelName);
+    }
+}
+
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ImageAnalysisUnavailableQuirk.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ImageAnalysisUnavailableQuirk.java
index a26fd3c..fddb34d 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ImageAnalysisUnavailableQuirk.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ImageAnalysisUnavailableQuirk.java
@@ -26,89 +26,67 @@
 import androidx.camera.core.Preview;
 import androidx.camera.core.impl.Quirk;
 import androidx.camera.extensions.ExtensionMode;
+import androidx.camera.extensions.internal.compat.workaround.ImageAnalysisAvailability;
 
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Set;
-
 /**
  * <p>QuirkSummary
- * Bug Id: b/290007642,
- * Description: When enabling Extensions on devices that implement the Basic Extender,
- * ImageAnalysis is assumed to be supported always. But this might be false on some devices like
- * Samsung Galaxy S23 Ultra 5G. This might cause preview black screen or unable to capture image
- * issues.
- * Device(s): Samsung Galaxy S23 Ultra 5G, Z Fold3 5G or A52s 5G devices
+ *     Bug Id: b/290007642,
+ *     Description: When enabling Extensions on devices that implement the Basic Extender,
+ *                  ImageAnalysis is assumed to be supported always. But this might be false on
+ *                  some devices like Samsung Galaxy S23 Ultra 5G, even if the device hardware
+ *                  level is FULL or above that should be able to support the additional
+ *                  ImageAnalysis no matter the Preview and ImageCapture have capture processor
+ *                  or not. This might cause preview black screen or unable to capture image issues.
+ *     Device(s): Samsung Galaxy S23 Ultra 5G, Z Fold3 5G, A52s 5G or S22 Ultra devices
+ *     @see ImageAnalysisAvailability
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public class ImageAnalysisUnavailableQuirk implements Quirk {
-
     private static final Set<Pair<String, String>> KNOWN_DEVICES = new HashSet<>(
             Arrays.asList(
                     Pair.create("samsung", "dm3q"), // Samsung Galaxy S23 Ultra 5G
                     Pair.create("samsung", "q2q"), // Samsung Galaxy Z Fold3 5G
                     Pair.create("samsung", "a52sxq"), // Samsung Galaxy A52s 5G
-                    Pair.create("samsung", "b0q"), // Samsung Galaxy S22 Ultra
-                    Pair.create("samsung", "gts8uwifi") // Samsung Galaxy Tab S8 Ultra
+                    Pair.create("samsung", "b0q") // Samsung Galaxy S22 Ultra
             ));
-
     private final Set<Pair<String, Integer>> mUnavailableCombinations = new HashSet<>();
-
     ImageAnalysisUnavailableQuirk() {
-        if (Build.BRAND.equalsIgnoreCase("SAMSUNG") && Build.DEVICE.equalsIgnoreCase("dm3q")) {
+        if (Build.BRAND.equalsIgnoreCase("SAMSUNG") && Build.DEVICE.equalsIgnoreCase(
+                "dm3q")) { // Samsung Galaxy S23 Ultra 5G
             mUnavailableCombinations.addAll(Arrays.asList(
-                    Pair.create("1", ExtensionMode.BOKEH),
+                    Pair.create("1", ExtensionMode.BOKEH), // LEVEL_FULL
                     Pair.create("1", ExtensionMode.FACE_RETOUCH),
-                    Pair.create("2", ExtensionMode.BOKEH),
-                    Pair.create("2", ExtensionMode.FACE_RETOUCH),
-                    Pair.create("3", ExtensionMode.BOKEH),
+                    Pair.create("3", ExtensionMode.BOKEH), // LEVEL_FULL
                     Pair.create("3", ExtensionMode.FACE_RETOUCH)
             ));
         } else if (Build.BRAND.equalsIgnoreCase("SAMSUNG") && Build.DEVICE.equalsIgnoreCase(
-                "q2q")) {
+                "q2q")) { // Samsung Galaxy Z Fold3 5G
             mUnavailableCombinations.addAll(Arrays.asList(
-                    Pair.create("0", ExtensionMode.BOKEH),
-                    Pair.create("0", ExtensionMode.FACE_RETOUCH),
-                    Pair.create("1", ExtensionMode.BOKEH),
-                    Pair.create("1", ExtensionMode.FACE_RETOUCH),
-                    Pair.create("2", ExtensionMode.BOKEH),
-                    Pair.create("2", ExtensionMode.FACE_RETOUCH),
-                    Pair.create("3", ExtensionMode.BOKEH),
-                    Pair.create("3", ExtensionMode.FACE_RETOUCH)
+                    Pair.create("0", ExtensionMode.BOKEH), // LEVEL_3
+                    Pair.create("0", ExtensionMode.FACE_RETOUCH)
             ));
         } else if (Build.BRAND.equalsIgnoreCase("SAMSUNG") && Build.DEVICE.equalsIgnoreCase(
-                "a52sxq")) {
+                "a52sxq")) { // Samsung Galaxy A52s 5G
             mUnavailableCombinations.addAll(Arrays.asList(
-                    Pair.create("0", ExtensionMode.BOKEH),
-                    Pair.create("0", ExtensionMode.FACE_RETOUCH),
-                    Pair.create("1", ExtensionMode.BOKEH),
-                    Pair.create("1", ExtensionMode.FACE_RETOUCH),
-                    Pair.create("2", ExtensionMode.BOKEH),
-                    Pair.create("2", ExtensionMode.FACE_RETOUCH),
-                    Pair.create("3", ExtensionMode.BOKEH),
-                    Pair.create("3", ExtensionMode.FACE_RETOUCH)
+                    Pair.create("0", ExtensionMode.BOKEH), // LEVEL_3
+                    Pair.create("0", ExtensionMode.FACE_RETOUCH)
             ));
         } else if (Build.BRAND.equalsIgnoreCase("SAMSUNG") && Build.DEVICE.equalsIgnoreCase(
-                "b0q")) {
+                "b0q")) { // Samsung Galaxy A52s 5G
             mUnavailableCombinations.addAll(Arrays.asList(
-                    Pair.create("3", ExtensionMode.BOKEH),
+                    Pair.create("3", ExtensionMode.BOKEH), // FULL
                     Pair.create("3", ExtensionMode.FACE_RETOUCH)
             ));
-        } else if (Build.BRAND.equalsIgnoreCase("SAMSUNG") && Build.DEVICE.equalsIgnoreCase(
-                "gts8uwifi")) {
-            mUnavailableCombinations.addAll(Arrays.asList(
-                    Pair.create("2", ExtensionMode.BOKEH),
-                    Pair.create("2", ExtensionMode.FACE_RETOUCH)
-            ));
         }
     }
-
     static boolean load() {
         return KNOWN_DEVICES.contains(Pair.create(Build.BRAND.toLowerCase(Locale.US),
                 Build.DEVICE.toLowerCase(Locale.US)));
     }
-
     /**
      * Returns whether {@link ImageAnalysis} is unavailable to be bound together with
      * {@link Preview} and {@link ImageCapture} for the specified camera id and extensions mode.
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/ImageAnalysisAvailability.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/ImageAnalysisAvailability.java
index 83d68c1..9500758 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/ImageAnalysisAvailability.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/ImageAnalysisAvailability.java
@@ -16,6 +16,10 @@
 
 package androidx.camera.extensions.internal.compat.workaround;
 
+import static android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_3;
+import static android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL;
+import static android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.ImageAnalysis;
@@ -23,31 +27,71 @@
 import androidx.camera.core.Preview;
 import androidx.camera.extensions.ExtensionMode;
 import androidx.camera.extensions.internal.compat.quirk.DeviceQuirks;
+import androidx.camera.extensions.internal.compat.quirk.ExtraSupportedSurfaceCombinationsQuirk;
 import androidx.camera.extensions.internal.compat.quirk.ImageAnalysisUnavailableQuirk;
 
 /**
  * Workaround to check whether {@link ImageAnalysis} can be bound together with {@link Preview} and
- * {@link ImageCapture} when
- * enabling extensions.
+ * {@link ImageCapture} when enabling extensions.
  *
- * @see ImageAnalysisUnavailableQuirk
+ * <p>This is used by the BasicVendorExtender to check whether the device can support to bind the
+ * additional ImageAnalysis UseCase.
+ *
+ * @see ImageAnalysisUnavailableQuirk, ExtraSupportedSurfaceCombinationsQuirk
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public class ImageAnalysisAvailability {
     ImageAnalysisUnavailableQuirk mImageAnalysisUnavailableQuirk =
             DeviceQuirks.get(ImageAnalysisUnavailableQuirk.class);
 
+    ExtraSupportedSurfaceCombinationsQuirk mExtraSupportedSurfaceCombinationsQuirk =
+            DeviceQuirks.get(ExtraSupportedSurfaceCombinationsQuirk.class);
+
     /**
-     * Returns whether {@link ImageAnalysis} is unavailable to be bound together with
+     * Returns whether {@link ImageAnalysis} is available to be bound together with
      * {@link Preview} and {@link ImageCapture} for the specified camera id and extensions mode.
      *
      * @param cameraId the camera id to query
+     * @param hardwareLevel the camera device hardware level
      * @param mode the extensions mode to query
-     * @return {@code true} if {@link ImageAnalysis} is unavailable. Otherwise, returns {@code
+     * @param hasPreviewProcessor whether PreviewExtenderImpl has processor
+     * @param hasImageCaptureProcessor whether ImageCaptureExtenderImpl has processor
+     * @return {@code true} if {@link ImageAnalysis} is available. Otherwise, returns {@code
      * false}.
      */
-    public boolean isUnavailable(@NonNull String cameraId, @ExtensionMode.Mode int mode) {
-        return mImageAnalysisUnavailableQuirk != null
-                && mImageAnalysisUnavailableQuirk.isUnavailable(cameraId, mode);
+    public boolean isAvailable(@NonNull String cameraId, int hardwareLevel,
+            @ExtensionMode.Mode int mode, boolean hasPreviewProcessor,
+            boolean hasImageCaptureProcessor) {
+        // When ImageAnalysisUnavailableQuirk is loaded and its isUnavailable() function returns
+        // true, directly return false that means the device can't support to bind ImageAnalysis
+        // when enabling the extensions mode.
+        if (mImageAnalysisUnavailableQuirk != null
+                && mImageAnalysisUnavailableQuirk.isUnavailable(cameraId, mode)) {
+            return false;
+        }
+
+        // No matter what the device hardware is and no matter the Preview and the ImageCapture
+        // have processor or not, once ExtraSupportedSurfaceCombinationsQuirk can be loaded, the
+        // device can support to bind ImageAnalysis when enabling the extensions mode.
+        if (mExtraSupportedSurfaceCombinationsQuirk != null) {
+            return true;
+        }
+
+        if (!hasPreviewProcessor && !hasImageCaptureProcessor) {
+            // Required configuration: PRIV + JPEG + YUV
+            // Required HW level: any
+            return true;
+        } else if (hasPreviewProcessor && !hasImageCaptureProcessor) {
+            // Required configuration: YUV + JPEG + YUV
+            // Required HW level: LIMITED level or above
+            return hardwareLevel == INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+                    || hardwareLevel == INFO_SUPPORTED_HARDWARE_LEVEL_FULL
+                    || hardwareLevel == INFO_SUPPORTED_HARDWARE_LEVEL_3;
+        } else {
+            // Required configuration: PRIV + YUV + YUV or YUV + YUV + YUV
+            // Required HW level: FULL level or above
+            return hardwareLevel == INFO_SUPPORTED_HARDWARE_LEVEL_FULL
+                    || hardwareLevel == INFO_SUPPORTED_HARDWARE_LEVEL_3;
+        }
     }
 }
diff --git a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/compat/workaround/ImageAnalysisAvailabilityTest.kt b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/compat/workaround/ImageAnalysisAvailabilityTest.kt
index bba6c8f..92166ed 100644
--- a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/compat/workaround/ImageAnalysisAvailabilityTest.kt
+++ b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/compat/workaround/ImageAnalysisAvailabilityTest.kt
@@ -16,8 +16,12 @@
 
 package androidx.camera.extensions.internal.compat.workaround
 
+import android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_3
+import android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
+import android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
 import android.os.Build
-import androidx.camera.extensions.ExtensionMode
+import androidx.camera.extensions.ExtensionMode.BOKEH
+import androidx.camera.extensions.ExtensionMode.FACE_RETOUCH
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,56 +42,69 @@
         // Set up device properties
         ReflectionHelpers.setStaticField(Build::class.java, "BRAND", config.brand)
         ReflectionHelpers.setStaticField(Build::class.java, "DEVICE", config.device)
+        ReflectionHelpers.setStaticField(Build::class.java, "MODEL", config.model)
         val imageAnalysisAvailability = ImageAnalysisAvailability()
-        assertThat(imageAnalysisAvailability.isUnavailable(config.cameraId, config.mode)).isEqualTo(
-            config.isUnavailable
+        assertThat(imageAnalysisAvailability.isAvailable(
+            config.cameraId,
+            config.hardwareLevel,
+            config.mode,
+            config.hasPreviewProcessor,
+            config.hasImageCaptureProcessor)).isEqualTo(
+            config.isAvailable
         )
     }
 
     class TestConfig(
         val brand: String,
         val device: String,
+        val model: String,
         val cameraId: String,
+        val hardwareLevel: Int,
         val mode: Int,
-        val isUnavailable: Boolean
+        val hasPreviewProcessor: Boolean,
+        val hasImageCaptureProcessor: Boolean,
+        val isAvailable: Boolean
     )
 
     companion object {
         @JvmStatic
         @ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
         fun createTestSet(): List<TestConfig> {
+            val levelLimited = INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+            val levelFull = INFO_SUPPORTED_HARDWARE_LEVEL_FULL
+            val level3 = INFO_SUPPORTED_HARDWARE_LEVEL_3
             return listOf(
                 // Samsung Galaxy S23 Ultra 5G tests
-                TestConfig("Samsung", "dm3q", "0", ExtensionMode.BOKEH, false),
-                TestConfig("Samsung", "dm3q", "1", ExtensionMode.BOKEH, true),
-                TestConfig("Samsung", "dm3q", "1", ExtensionMode.FACE_RETOUCH, true),
-                TestConfig("Samsung", "dm3q", "1", ExtensionMode.HDR, false),
+                TestConfig("Samsung", "dm3q", "", "0", level3, BOKEH, true, true, true),
+                TestConfig("Samsung", "dm3q", "", "1", levelFull, BOKEH, true, true, false),
+                TestConfig("Samsung", "dm3q", "", "1", levelFull, FACE_RETOUCH, true, true, false),
+                TestConfig("Samsung", "dm3q", "", "2", levelLimited, BOKEH, true, true, false),
 
                 // Samsung Galaxy Z Fold3 5G
-                TestConfig("Samsung", "q2q", "0", ExtensionMode.BOKEH, true),
-                TestConfig("Samsung", "q2q", "0", ExtensionMode.FACE_RETOUCH, true),
-                TestConfig("Samsung", "q2q", "0", ExtensionMode.HDR, false),
+                TestConfig("Samsung", "q2q", "", "0", level3, BOKEH, true, true, false),
+                TestConfig("Samsung", "q2q", "", "0", level3, FACE_RETOUCH, true, true, false),
+                TestConfig("Samsung", "q2q", "", "1", levelLimited, BOKEH, true, true, false),
 
                 // Samsung Galaxy A52s 5G
-                TestConfig("Samsung", "a52sxq", "0", ExtensionMode.BOKEH, true),
-                TestConfig("Samsung", "a52sxq", "0", ExtensionMode.FACE_RETOUCH, true),
-                TestConfig("Samsung", "a52sxq", "0", ExtensionMode.HDR, false),
+                TestConfig("Samsung", "a52sxq", "", "0", level3, BOKEH, true, true, false),
+                TestConfig("Samsung", "a52sxq", "", "0", level3, BOKEH, true, true, false),
+                TestConfig("Samsung", "a52sxq", "", "1", levelLimited, BOKEH, true, true, false),
 
-                // Samsung Galaxy S22 Ultra
-                TestConfig("Samsung", "b0q", "0", ExtensionMode.BOKEH, false),
-                TestConfig("Samsung", "b0q", "3", ExtensionMode.BOKEH, true),
-                TestConfig("Samsung", "b0q", "3", ExtensionMode.FACE_RETOUCH, true),
-                TestConfig("Samsung", "b0q", "3", ExtensionMode.HDR, false),
+                // Samsung Galaxy S22 Ultra tests
+                TestConfig("Samsung", "b0q", "", "0", level3, BOKEH, true, true, true),
+                TestConfig("Samsung", "b0q", "", "3", levelFull, BOKEH, true, true, false),
+                TestConfig("Samsung", "b0q", "", "3", levelFull, FACE_RETOUCH, true, true, false),
 
-                // Samsung Galaxy Tab S8 Ultra
-                TestConfig("Samsung", "gts8uwifi", "0", ExtensionMode.BOKEH, false),
-                TestConfig("Samsung", "gts8uwifi", "2", ExtensionMode.BOKEH, true),
-                TestConfig("Samsung", "gts8uwifi", "2", ExtensionMode.FACE_RETOUCH, true),
-                TestConfig("Samsung", "gts8uwifi", "2", ExtensionMode.HDR, false),
+                // Samsung Galaxy A51 (support extra full level surface combinations)
+                TestConfig("Samsung", "", "SM-A515F", "1", levelLimited, BOKEH, true, true, true),
 
-                // Other cases should be kept available.
-                TestConfig("", "", "0", ExtensionMode.BOKEH, false),
-                TestConfig("", "", "1", ExtensionMode.NONE, false),
+                // Other cases should be determined by hardware level and processors.
+                TestConfig("", "", "", "0", level3, BOKEH, true, true, true),
+                TestConfig("", "", "", "0", levelFull, BOKEH, true, true, true),
+                TestConfig("", "", "", "0", levelFull, BOKEH, false, true, true),
+                TestConfig("", "", "", "0", levelLimited, BOKEH, true, true, false),
+                TestConfig("", "", "", "0", levelLimited, BOKEH, true, false, true),
+                TestConfig("", "", "", "0", levelLimited, BOKEH, false, true, false),
             )
         }
     }
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java
index 7a4b53a..8b28e6c 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraInfoInternal.java
@@ -299,6 +299,16 @@
         return mIntrinsicZoomRatio;
     }
 
+    @Override
+    public boolean isPreviewStabilizationSupported() {
+        return false;
+    }
+
+    @Override
+    public boolean isVideoStabilizationSupported() {
+        return false;
+    }
+
     /** Adds a quirk to the list of this camera's quirks. */
     @SuppressWarnings("unused")
     public void addCameraQuirk(@NonNull final Quirk quirk) {
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/E2ETestUtil.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/FileUtil.kt
similarity index 60%
rename from camera/camera-testing/src/main/java/androidx/camera/testing/impl/E2ETestUtil.kt
rename to camera/camera-testing/src/main/java/androidx/camera/testing/impl/FileUtil.kt
index e454f702..b482047 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/E2ETestUtil.kt
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/FileUtil.kt
@@ -18,10 +18,12 @@
 
 import android.content.ContentResolver
 import android.content.ContentValues
+import android.net.Uri
 import android.os.Environment.DIRECTORY_DOCUMENTS
 import android.os.Environment.DIRECTORY_MOVIES
 import android.os.Environment.getExternalStoragePublicDirectory
 import android.provider.MediaStore
+import android.util.Log
 import androidx.annotation.RequiresApi
 import androidx.camera.core.Logger
 import androidx.camera.video.FileOutputOptions
@@ -32,12 +34,12 @@
 import java.io.FileOutputStream
 import java.io.OutputStreamWriter
 
-private const val TAG = "E2ETestUtil"
+private const val TAG = "FileUtil"
 private const val EXTENSION_MP4 = "mp4"
 private const val EXTENSION_TEXT = "txt"
 
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-object E2ETestUtil {
+object FileUtil {
 
     /**
      * Write the given text to the external storage.
@@ -101,7 +103,7 @@
     ): FileOutputOptions {
         val fileNameWithExtension = "$fileName.$extension"
         val folder = getExternalStoragePublicDirectory(DIRECTORY_MOVIES)
-        if (!folder.exists() && !folder.mkdirs()) {
+        if (!createFolder(folder)) {
             Logger.e(TAG, "Failed to create directory: $folder")
         }
         return FileOutputOptions.Builder(File(folder, fileNameWithExtension)).build()
@@ -119,14 +121,10 @@
     fun generateVideoMediaStoreOptions(
         contentResolver: ContentResolver,
         fileName: String
-    ): MediaStoreOutputOptions {
-        val contentValues = generateVideoContentValues(fileName)
-
-        return MediaStoreOutputOptions.Builder(
-            contentResolver,
-            MediaStore.Video.Media.EXTERNAL_CONTENT_URI
-        ).setContentValues(contentValues).build()
-    }
+    ): MediaStoreOutputOptions = MediaStoreOutputOptions.Builder(
+        contentResolver,
+        MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+    ).setContentValues(generateVideoContentValues(fileName)).build()
 
     /**
      * Check if the given file name string is valid.
@@ -145,14 +143,79 @@
         return !fileName.isNullOrBlank()
     }
 
-    private fun generateVideoContentValues(fileName: String): ContentValues {
-        val res = ContentValues()
-        res.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
-        res.put(MediaStore.Video.Media.TITLE, fileName)
-        res.put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
-        res.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000)
-        res.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis())
+    /**
+     * Creates parent folder for the input file path.
+     *
+     * @param filePath the input file path to create its parent folder.
+     * @return `true` if the parent folder already exists or is created successfully.
+     * `false` if the existing parent folder path is not a folder or failed to create.
+     */
+    @JvmStatic
+    fun createParentFolder(filePath: String): Boolean {
+        return createParentFolder(File(filePath))
+    }
 
-        return res
+    /**
+     * Creates parent folder for the input file.
+     *
+     * @param file the input file to create its parent folder
+     * @return `true` if the parent folder already exists or is created successfully.
+     * `false` if the existing parent folder path is not a folder or failed to create.
+     */
+    @JvmStatic
+    fun createParentFolder(file: File): Boolean = file.parentFile?.let {
+        createFolder(it)
+    } ?: false
+
+    /**
+     * Creates folder for the input file.
+     *
+     * @param file the input file to create folder
+     * @return `true` if the folder already exists or is created successfully.
+     * `false` if the existing folder path is not a folder or failed to create.
+     */
+    @JvmStatic
+    fun createFolder(file: File): Boolean = if (file.exists()) {
+        file.isDirectory
+    } else {
+        file.mkdirs()
+    }
+
+    /**
+     * Gets the absolute path from a Uri.
+     *
+     * @param resolver   the content resolver.
+     * @param contentUri the content uri.
+     * @return the file path of the content uri or null if not found.
+     */
+    @JvmStatic
+    fun getAbsolutePathFromUri(resolver: ContentResolver, contentUri: Uri): String? {
+        // MediaStore.Video.Media.DATA was deprecated in API level 29.
+        val column = MediaStore.Video.Media.DATA
+        try {
+            resolver.query(contentUri, arrayOf(column), null, null, null)!!.use { cursor ->
+                val columnIndex = cursor.getColumnIndexOrThrow(column)
+                cursor.moveToFirst()
+                return cursor.getString(columnIndex)
+            }
+        } catch (e: RuntimeException) {
+            Log.e(
+                TAG,
+                String.format(
+                    "Failed in getting absolute path for Uri %s with Exception %s",
+                    contentUri, e
+                ), e
+            )
+            return null
+        }
+    }
+
+    private fun generateVideoContentValues(fileName: String) = ContentValues().apply {
+        put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
+        put(MediaStore.Video.Media.TITLE, fileName)
+        put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
+        val currentTimeMs = System.currentTimeMillis()
+        put(MediaStore.Video.Media.DATE_ADDED, currentTimeMs / 1000)
+        put(MediaStore.Video.Media.DATE_TAKEN, currentTimeMs)
     }
 }
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraDeviceSurfaceManager.java b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraDeviceSurfaceManager.java
index c0a5fe5..4813cdc 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraDeviceSurfaceManager.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/fakes/FakeCameraDeviceSurfaceManager.java
@@ -18,9 +18,7 @@
 
 import static android.graphics.ImageFormat.JPEG;
 import static android.graphics.ImageFormat.YUV_420_888;
-
 import static androidx.camera.core.impl.ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE;
-
 import static com.google.common.primitives.Ints.asList;
 
 import android.util.Pair;
@@ -90,7 +88,8 @@
             @CameraMode.Mode int cameraMode,
             @NonNull String cameraId,
             @NonNull List<AttachedSurfaceInfo> existingSurfaces,
-            @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap) {
+            @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap,
+            boolean isPreviewStabilizationOn) {
         List<UseCaseConfig<?>> newUseCaseConfigs =
                 new ArrayList<>(newUseCaseConfigsSupportedSizeMap.keySet());
         checkSurfaceCombo(existingSurfaces, newUseCaseConfigs);
diff --git a/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManagerTest.java b/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManagerTest.java
index 453a9af..1ac4332 100644
--- a/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManagerTest.java
+++ b/camera/camera-testing/src/test/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManagerTest.java
@@ -17,12 +17,9 @@
 package androidx.camera.testing.fakes;
 
 import static android.graphics.ImageFormat.YUV_420_888;
-
 import static androidx.camera.core.impl.SurfaceConfig.ConfigSize.PREVIEW;
 import static androidx.camera.core.impl.SurfaceConfig.ConfigType.YUV;
-
 import static com.google.common.truth.Truth.assertThat;
-
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
 
@@ -96,7 +93,8 @@
                 CameraMode.DEFAULT,
                 FAKE_CAMERA_ID0,
                 emptyList(),
-                createConfigOutputSizesMap(preview, analysis));
+                createConfigOutputSizesMap(preview, analysis),
+                false);
     }
 
     @Test(expected = IllegalArgumentException.class)
@@ -114,7 +112,8 @@
         mFakeCameraDeviceSurfaceManager.getSuggestedStreamSpecs(
                 CameraMode.DEFAULT,
                 FAKE_CAMERA_ID0,
-                singletonList(analysis), createConfigOutputSizesMap(preview, video));
+                singletonList(analysis), createConfigOutputSizesMap(preview, video),
+                false);
     }
 
     @Test(expected = IllegalArgumentException.class)
@@ -125,7 +124,8 @@
         mFakeCameraDeviceSurfaceManager.getSuggestedStreamSpecs(
                 CameraMode.DEFAULT,
                 FAKE_CAMERA_ID0,
-                Collections.emptyList(), createConfigOutputSizesMap(preview, video, analysis));
+                Collections.emptyList(), createConfigOutputSizesMap(preview, video, analysis),
+                false);
     }
 
     @Test
@@ -134,12 +134,14 @@
                 mFakeCameraDeviceSurfaceManager.getSuggestedStreamSpecs(
                         CameraMode.DEFAULT,
                         FAKE_CAMERA_ID0,
-                        emptyList(), createConfigOutputSizesMap(mFakeUseCaseConfig)).first;
+                        emptyList(), createConfigOutputSizesMap(mFakeUseCaseConfig),
+                        false).first;
         Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecCamera1 =
                 mFakeCameraDeviceSurfaceManager.getSuggestedStreamSpecs(
                         CameraMode.DEFAULT,
                         FAKE_CAMERA_ID1,
-                        emptyList(), createConfigOutputSizesMap(mFakeUseCaseConfig)).first;
+                        emptyList(), createConfigOutputSizesMap(mFakeUseCaseConfig),
+                        false).first;
 
         assertThat(suggestedStreamSpecsCamera0.get(mFakeUseCaseConfig)).isEqualTo(
                 StreamSpec.builder(new Size(FAKE_WIDTH0, FAKE_HEIGHT0)).build());
diff --git a/camera/camera-video/api/current.txt b/camera/camera-video/api/current.txt
index b9cc77a..7f1314e 100644
--- a/camera/camera-video/api/current.txt
+++ b/camera/camera-video/api/current.txt
@@ -2,10 +2,12 @@
 package androidx.camera.video {
 
   @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class AudioStats {
+    method public double getAudioAmplitude();
     method public abstract int getAudioState();
     method public abstract Throwable? getErrorCause();
     method public boolean hasAudio();
     method public boolean hasError();
+    field public static final double AUDIO_AMPLITUDE_NONE = 0.0;
     field public static final int AUDIO_STATE_ACTIVE = 0; // 0x0
     field public static final int AUDIO_STATE_DISABLED = 1; // 0x1
     field public static final int AUDIO_STATE_ENCODER_ERROR = 3; // 0x3
@@ -14,6 +16,9 @@
     field public static final int AUDIO_STATE_SOURCE_SILENCED = 2; // 0x2
   }
 
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalAudioApi {
+  }
+
   @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalPersistentRecording {
   }
 
diff --git a/camera/camera-video/api/restricted_current.txt b/camera/camera-video/api/restricted_current.txt
index b9cc77a..7f1314e 100644
--- a/camera/camera-video/api/restricted_current.txt
+++ b/camera/camera-video/api/restricted_current.txt
@@ -2,10 +2,12 @@
 package androidx.camera.video {
 
   @RequiresApi(21) @com.google.auto.value.AutoValue public abstract class AudioStats {
+    method public double getAudioAmplitude();
     method public abstract int getAudioState();
     method public abstract Throwable? getErrorCause();
     method public boolean hasAudio();
     method public boolean hasError();
+    field public static final double AUDIO_AMPLITUDE_NONE = 0.0;
     field public static final int AUDIO_STATE_ACTIVE = 0; // 0x0
     field public static final int AUDIO_STATE_DISABLED = 1; // 0x1
     field public static final int AUDIO_STATE_ENCODER_ERROR = 3; // 0x3
@@ -14,6 +16,9 @@
     field public static final int AUDIO_STATE_SOURCE_SILENCED = 2; // 0x2
   }
 
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalAudioApi {
+  }
+
   @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalPersistentRecording {
   }
 
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoCaptureDeviceTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoCaptureDeviceTest.kt
index 42f8ae0..3e39ad6 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoCaptureDeviceTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoCaptureDeviceTest.kt
@@ -570,6 +570,10 @@
                 val profiles = createFakeEncoderProfilesProxy(size.width, size.height)
                 return VideoValidatedEncoderProfilesProxy.from(profiles)
             }
+
+            override fun isStabilizationSupported(): Boolean {
+                return false
+            }
         }
     }
 
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/AudioStats.java b/camera/camera-video/src/main/java/androidx/camera/video/AudioStats.java
index 7f171f5..dde69be 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/AudioStats.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/AudioStats.java
@@ -104,7 +104,6 @@
      * Should audio recording be disabled, any attempts to retrieve the amplitude will
      * return this value.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public static final double AUDIO_AMPLITUDE_NONE = 0;
 
     @IntDef({AUDIO_STATE_ACTIVE, AUDIO_STATE_DISABLED, AUDIO_STATE_SOURCE_SILENCED,
@@ -168,8 +167,8 @@
     public abstract Throwable getErrorCause();
 
     /**
-     * Returns the maximum absolute amplitude of the audio most recently sampled. Returns
-     * {@link #AUDIO_AMPLITUDE_NONE} if audio is disabled.
+     * Returns the maximum absolute amplitude of the audio most recently sampled in the past 2
+     * nanoseconds
      *
      * <p>The amplitude is the maximum absolute value over all channels which the audio was
      * most recently sampled from.
@@ -177,10 +176,11 @@
      * <p>Amplitude is a relative measure of the maximum sound pressure/voltage range of the device
      * microphone.
      *
+     * <p>Returns {@link #AUDIO_AMPLITUDE_NONE} if audio is disabled.
      * <p>The amplitude value returned will be a double between {@code 0} and {@code 1}.
+     *
      */
     @OptIn(markerClass = ExperimentalAudioApi.class)
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public double getAudioAmplitude() {
         if (getAudioState() == AUDIO_STATE_DISABLED) {
             return AUDIO_AMPLITUDE_NONE;
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/ExperimentalAudioApi.java b/camera/camera-video/src/main/java/androidx/camera/video/ExperimentalAudioApi.java
index bf07a6e..99de642 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/ExperimentalAudioApi.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/ExperimentalAudioApi.java
@@ -19,16 +19,14 @@
 import static java.lang.annotation.RetentionPolicy.CLASS;
 
 import androidx.annotation.RequiresOptIn;
-import androidx.annotation.RestrictTo;
 
 import java.lang.annotation.Retention;
 
 /**
- * Denotes that the methods on retrieving audio amplitude data are experimental and may
+ * Denotes that the annotated element relates to an experimental audio feature and may
  * change in a future release.
  */
 @Retention(CLASS)
 @RequiresOptIn
-@RestrictTo(RestrictTo.Scope.LIBRARY)
 public @interface ExperimentalAudioApi {
 }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/RecorderVideoCapabilities.java b/camera/camera-video/src/main/java/androidx/camera/video/RecorderVideoCapabilities.java
index 7b78e91..b9e560e 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/RecorderVideoCapabilities.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/RecorderVideoCapabilities.java
@@ -76,6 +76,8 @@
 
     private final EncoderProfilesProvider mProfilesProvider;
 
+    private boolean mIsStabilizationSupported = false;
+
     // Mappings of DynamicRange to recording capability information. The mappings are divided
     // into two collections based on the key's (DynamicRange) category, one for specified
     // DynamicRange and one for others. Specified DynamicRange means that its bit depth and
@@ -130,6 +132,9 @@
                 mCapabilitiesMapForFullySpecifiedDynamicRange.put(dynamicRange, capabilities);
             }
         }
+
+        // Video stabilization
+        mIsStabilizationSupported = cameraInfoInternal.isVideoStabilizationSupported();
     }
 
     /**
@@ -166,6 +171,11 @@
         return capabilities != null && capabilities.isQualitySupported(quality);
     }
 
+    @Override
+    public boolean isStabilizationSupported() {
+        return mIsStabilizationSupported;
+    }
+
     @Nullable
     @Override
     public VideoValidatedEncoderProfilesProxy getProfiles(@NonNull Quality quality,
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java
index 02b857e..8f9fd80 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapabilities.java
@@ -16,6 +16,7 @@
 
 package androidx.camera.video;
 
+import android.hardware.camera2.CaptureRequest;
 import android.util.Size;
 
 import androidx.annotation.NonNull;
@@ -114,6 +115,17 @@
     boolean isQualitySupported(@NonNull Quality quality, @NonNull DynamicRange dynamicRange);
 
     /**
+     * Returns if video stabilization is supported on the device.
+     *
+     * @return true if {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE_ON} is supported,
+     * otherwise false.
+     *
+     * @see CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    boolean isStabilizationSupported();
+
+    /**
      * Gets the corresponding {@link VideoValidatedEncoderProfilesProxy} of the input quality and
      * dynamic range.
      *
@@ -193,5 +205,10 @@
                 @NonNull DynamicRange dynamicRange) {
             return false;
         }
+
+        @Override
+        public boolean isStabilizationSupported() {
+            return false;
+        }
     };
 }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
index 1b93298..8e8bbd6 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
@@ -35,6 +35,7 @@
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_SESSION_CONFIG_UNPACKER;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_SURFACE_OCCUPANCY_PRIORITY;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_TARGET_FRAME_RATE;
+import static androidx.camera.core.impl.UseCaseConfig.OPTION_VIDEO_STABILIZATION_MODE;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_ZSL_DISABLED;
 import static androidx.camera.core.impl.utils.Threads.isMainThread;
 import static androidx.camera.core.impl.utils.TransformUtils.rectToString;
@@ -104,6 +105,7 @@
 import androidx.camera.core.impl.Timebase;
 import androidx.camera.core.impl.UseCaseConfig;
 import androidx.camera.core.impl.UseCaseConfigFactory;
+import androidx.camera.core.impl.stabilization.StabilizationMode;
 import androidx.camera.core.impl.utils.Threads;
 import androidx.camera.core.impl.utils.TransformUtils;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
@@ -282,6 +284,14 @@
     }
 
     /**
+     * Returns whether video stabilization is enabled.
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    public boolean isVideoStabilizationEnabled() {
+        return getCurrentConfig().getVideoStabilizationMode() == StabilizationMode.ON;
+    }
+
+    /**
      * Sets the desired rotation of the output video.
      *
      * <p>Valid values include: {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
@@ -684,6 +694,7 @@
         // Use the frame rate range directly from the StreamSpec here (don't resolve it to the
         // default if unresolved).
         sessionConfigBuilder.setExpectedFrameRateRange(streamSpec.getExpectedFrameRateRange());
+        sessionConfigBuilder.setVideoStabilization(config.getVideoStabilizationMode());
         sessionConfigBuilder.addErrorListener(
                 (sessionConfig, error) -> resetPipeline(cameraId, config, streamSpec));
         if (USE_TEMPLATE_PREVIEW_BY_QUIRK) {
@@ -894,7 +905,7 @@
 
         sessionConfigBuilder.clearSurfaces();
         DynamicRange dynamicRange = streamSpec.getDynamicRange();
-        if (!isStreamError) {
+        if (!isStreamError && mDeferrableSurface != null) {
             if (isStreamActive) {
                 sessionConfigBuilder.addSurface(mDeferrableSurface, dynamicRange);
             } else {
@@ -1798,6 +1809,20 @@
             return this;
         }
 
+        /**
+         * Enable video stabilization.
+         *
+         * @param enabled True if enable, otherwise false.
+         * @return the current Builder.
+         */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public Builder<T> setVideoStabilizationEnabled(boolean enabled) {
+            getMutableConfig().insertOption(OPTION_VIDEO_STABILIZATION_MODE,
+                    enabled ? StabilizationMode.ON : StabilizationMode.OFF);
+            return this;
+        }
+
         @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
         @Override
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/PreviewDelayWhenVideoCaptureIsBoundQuirk.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/PreviewDelayWhenVideoCaptureIsBoundQuirk.java
index efffa0c..558d22d 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/PreviewDelayWhenVideoCaptureIsBoundQuirk.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/PreviewDelayWhenVideoCaptureIsBoundQuirk.java
@@ -21,22 +21,20 @@
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.impl.Quirk;
 
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Set;
-
 /**
  * <p>QuirkSummary
  *     Bug Id: b/223643510
  *     Description: Quirk indicates Preview is delayed on some Huawei devices when the Preview uses
- *                  certain resolutions and VideoCapture is bound.
+ *                  certain resolutions and VideoCapture is bound. The quirk applies on all
+ *                  Huawei devices since there is a certain number of unknown devices with this
+ *                  issue.
  *     Device(s): Some Huawei devices.
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public class PreviewDelayWhenVideoCaptureIsBoundQuirk implements Quirk {
 
-    private static final Set<String> HUAWEI_DEVICE_LIST = new HashSet<>(Arrays.asList(
+    /*
+    Known devices:
             "HWELE",  // P30
             "HW-02L", // P30 Pro
             "HWVOG",  // P30 Pro
@@ -44,15 +42,16 @@
             "HWLYA",  // Mate 20 Pro
             "HWCOL",  // Honor 10
             "HWPAR"   // Nova 3
-    ));
 
-    private static final Set<String> HUAWEI_MODEL_LIST = new HashSet<>(Arrays.asList(
+    Known models:
             "ELS-AN00", "ELS-TN00", "ELS-NX9", "ELS-N04"  // P40 Pro
-    ));
+
+    Known others:
+            mate40
+            honor v2
+     */
 
     static boolean load() {
-        return "Huawei".equalsIgnoreCase(Build.MANUFACTURER)
-                && (HUAWEI_DEVICE_LIST.contains(Build.DEVICE.toUpperCase(Locale.US))
-                || HUAWEI_MODEL_LIST.contains(Build.MODEL.toUpperCase(Locale.US)));
+        return "Huawei".equalsIgnoreCase(Build.MANUFACTURER);
     }
 }
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/QualitySelectorTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/QualitySelectorTest.kt
index 9fef206..78c7b9a 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/QualitySelectorTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/QualitySelectorTest.kt
@@ -438,6 +438,10 @@
             override fun isQualitySupported(quality: Quality, dynamicRange: DynamicRange): Boolean {
                 throw UnsupportedOperationException("Not supported.")
             }
+
+            override fun isStabilizationSupported(): Boolean {
+                return false
+            }
         }
     }
 }
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
index 6f0e1a9..734deea 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
@@ -1354,6 +1354,14 @@
         ).isEqualTo(newImplementationOptionValue)
     }
 
+    @Test
+    fun canSetVideoStabilization() {
+        val videoCapture = VideoCapture.Builder(Recorder.Builder().build())
+            .setVideoStabilizationEnabled(true)
+            .build()
+        assertThat(videoCapture.isVideoStabilizationEnabled).isTrue()
+    }
+
     private fun testSurfaceRequestContainsExpected(
         quality: Quality = HD, // HD maps to 1280x720 (4:3)
         videoEncoderInfo: VideoEncoderInfo = createVideoEncoderInfo(),
@@ -1708,6 +1716,10 @@
                     return videoCapabilitiesMap[dynamicRange]?.isQualitySupported(quality) ?: false
                 }
 
+                override fun isStabilizationSupported(): Boolean {
+                    return false
+                }
+
                 override fun getProfiles(
                     quality: Quality,
                     dynamicRange: DynamicRange
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
index d92bad2..b76e3c6 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
@@ -655,13 +655,17 @@
     /**
      * Captures a new still image and saves to a file along with application specified metadata.
      *
-     * <p> The callback will be called only once for every invocation of this method.
+     * <p>The callback will be called only once for every invocation of this method.
      *
-     * <p> By default, the saved image is mirrored to match the output of the preview if front
+     * <p>By default, the saved image is mirrored to match the output of the preview if front
      * camera is used. To override this behavior, the app needs to explicitly set the flag to
      * {@code false} using {@link ImageCapture.Metadata#setReversedHorizontal} and
      * {@link ImageCapture.OutputFileOptions.Builder#setMetadata}.
      *
+     * <p>The saved image is cropped to match the aspect ratio of the {@link PreviewView}. To
+     * take a picture with the maximum available resolution, make sure that the
+     * {@link PreviewView}'s aspect ratio is 4:3.
+     *
      * @param outputFileOptions  Options to store the newly captured image.
      * @param executor           The executor in which the callback methods will be run.
      * @param imageSavedCallback Callback to be called for the newly captured image.
@@ -1773,8 +1777,8 @@
      * <p>Valid zoom values range from {@link ZoomState#getMinZoomRatio()} to
      * {@link ZoomState#getMaxZoomRatio()}.
      *
-     * <p> No-ops if the camera is not ready. The {@link ListenableFuture} completes successfully
-     * in this case.
+     * <p>If the value is set before the camera is ready, {@link CameraController} waits for the
+     * camera to be ready and then sets the zoom ratio.
      *
      * @param zoomRatio The requested zoom ratio.
      * @return a {@link ListenableFuture} which is finished when camera is set to the given ratio.
@@ -1797,13 +1801,13 @@
     /**
      * Sets current zoom by a linear zoom value ranging from 0f to 1.0f.
      *
-     * <p> LinearZoom 0f represents the minimum zoom while linearZoom 1.0f represents the maximum
+     * <p>LinearZoom 0f represents the minimum zoom while linearZoom 1.0f represents the maximum
      * zoom. The advantage of linearZoom is that it ensures the field of view (FOV) varies
      * linearly with the linearZoom value, for use with slider UI elements (while
      * {@link #setZoomRatio(float)} works well for pinch-zoom gestures).
      *
-     * <p> No-ops if the camera is not ready. The {@link ListenableFuture} completes successfully
-     * in this case.
+     * <p>If the value is set before the camera is ready, {@link CameraController} waits for the
+     * camera to be ready and then sets the linear zoom.
      *
      * @return a {@link ListenableFuture} which is finished when camera is set to the given ratio.
      * It fails with {@link CameraControl.OperationCanceledException} if there is newer value
@@ -1840,8 +1844,8 @@
     /**
      * Enable the torch or disable the torch.
      *
-     * <p> No-ops if the camera is not ready. The {@link ListenableFuture} completes successfully
-     * in this case.
+     * <p>If the value is set before the camera is ready, {@link CameraController} waits for the
+     * camera to be ready and then enables the torch.
      *
      * @param torchEnabled true to turn on the torch, false to turn it off.
      * @return A {@link ListenableFuture} which is successful when the torch was changed to the
diff --git a/camera/integration-tests/coretestapp/build.gradle b/camera/integration-tests/coretestapp/build.gradle
index 2e5a792..410eb09 100644
--- a/camera/integration-tests/coretestapp/build.gradle
+++ b/camera/integration-tests/coretestapp/build.gradle
@@ -72,6 +72,7 @@
     implementation(libs.guavaAndroid)
     implementation(libs.espressoIdlingResource)
     implementation("androidx.appcompat:appcompat:1.3.0")
+    implementation("androidx.lifecycle:lifecycle-service:2.2.0")
     // MLKit library: Barcode scanner
     implementation(libs.mlkitBarcode) {
         exclude group: "androidx.fragment"
diff --git a/camera/integration-tests/coretestapp/lint-baseline.xml b/camera/integration-tests/coretestapp/lint-baseline.xml
index 4cbca68..9e9f16f 100644
--- a/camera/integration-tests/coretestapp/lint-baseline.xml
+++ b/camera/integration-tests/coretestapp/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.2.0-alpha15" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-alpha15)" variant="all" version="8.2.0-alpha15">
+<issues format="6" by="lint 8.2.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-beta01)" variant="all" version="8.2.0-beta01">
 
     <issue
         id="BanThreadSleep"
@@ -12,108 +12,90 @@
 
     <issue
         id="RestrictedApiAndroidX"
-        message="CameraInfoInternal can only be accessed from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="                if (cameraInfo instanceof CameraInfoInternal) {"
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~">
+        message="FileUtil.canDeviceWriteToMediaStore can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                    if (!canDeviceWriteToMediaStore()) {"
+        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="DeviceQuirks.getAll can only be called from within the same library (androidx.camera:camera-video)"
-        errorLine1="                    Quirks deviceQuirks = DeviceQuirks.getAll();"
-        errorLine2="                                                       ~~~~~~">
+        message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                                this, generateVideoFileOutputOptions(fileName, extension));"
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="CameraInfoInternal can only be accessed from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="                    Quirks cameraQuirks = ((CameraInfoInternal) cameraInfo).getCameraQuirks();"
-        errorLine2="                                            ~~~~~~~~~~~~~~~~~~">
+        message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                                this, generateVideoFileOutputOptions(fileName, extension));"
+        errorLine2="                                                                     ~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="CameraInfoInternal.getCameraQuirks can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="                    Quirks cameraQuirks = ((CameraInfoInternal) cameraInfo).getCameraQuirks();"
-        errorLine2="                                                                            ~~~~~~~~~~~~~~~">
+        message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                                this, generateVideoFileOutputOptions(fileName, extension));"
+        errorLine2="                                                                               ~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="Quirks.contains can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="                    if (deviceQuirks.contains(CrashWhenTakingPhotoWithAutoFlashAEModeQuirk.class)"
-        errorLine2="                                     ~~~~~~~~">
+        message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                                generateVideoMediaStoreOptions(getContentResolver(), fileName));"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="Quirks.contains can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="                    if (deviceQuirks.contains(CrashWhenTakingPhotoWithAutoFlashAEModeQuirk.class)"
-        errorLine2="                                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                                generateVideoMediaStoreOptions(getContentResolver(), fileName));"
+        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="Quirks.contains can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="                            || cameraQuirks.contains(ImageCaptureFailWithAutoFlashQuirk.class)"
-        errorLine2="                                            ~~~~~~~~">
+        message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                                generateVideoMediaStoreOptions(getContentResolver(), fileName));"
+        errorLine2="                                                                                     ~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="Quirks.contains can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="                            || cameraQuirks.contains(ImageCaptureFailWithAutoFlashQuirk.class)"
-        errorLine2="                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                        videoFilePath = getAbsolutePathFromUri("
+        errorLine2="                                        ~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="Quirks.contains can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="                            || cameraQuirks.contains(ImageCaptureFlashNotFireQuirk.class)) {"
-        errorLine2="                                            ~~~~~~~~">
+        message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                                getApplicationContext().getContentResolver(),"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="Quirks.contains can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="                            || cameraQuirks.contains(ImageCaptureFlashNotFireQuirk.class)) {"
-        errorLine2="                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="DeviceQuirks.get can only be called from within the same library (androidx.camera:camera-video)"
-        errorLine1="                    if (DeviceQuirks.get(MediaStoreVideoCannotWrite.class) != null) {"
-        errorLine2="                                     ~~~">
-        <location
-            file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="DeviceQuirks.get can only be called from within the same library (androidx.camera:camera-video)"
-        errorLine1="                    if (DeviceQuirks.get(MediaStoreVideoCannotWrite.class) != null) {"
-        errorLine2="                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                                uri"
+        errorLine2="                                ~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
     </issue>
@@ -183,6 +165,69 @@
 
     <issue
         id="RestrictedApiAndroidX"
+        message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        if (createParentFolder(pictureFolder)) {"
+        errorLine2="            ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        if (createParentFolder(pictureFolder)) {"
+        errorLine2="                               ~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                getAbsolutePathFromUri(getApplicationContext().getContentResolver(),"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                getAbsolutePathFromUri(getApplicationContext().getContentResolver(),"
+        errorLine2="                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                        MediaStore.Video.Media.EXTERNAL_CONTENT_URI);"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        if (videoFilePath == null || !createParentFolder(videoFilePath)) {"
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        if (videoFilePath == null || !createParentFolder(videoFilePath)) {"
+        errorLine2="                                                         ~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
         message="CameraXExecutors.mainThreadExecutor can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
         errorLine1="                            CameraXExecutors.mainThreadExecutor());"
         errorLine2="                                             ~~~~~~~~~~~~~~~~~~">
@@ -192,6 +237,159 @@
 
     <issue
         id="RestrictedApiAndroidX"
+        message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        if (!createParentFolder(pictureFolder)) {"
+        errorLine2="             ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        if (!createParentFolder(pictureFolder)) {"
+        errorLine2="                                ~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.canDeviceWriteToMediaStore can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="            if (!canDeviceWriteToMediaStore()) {"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                        this, generateVideoFileOutputOptions(fileName, extension));"
+        errorLine2="                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                        this, generateVideoFileOutputOptions(fileName, extension));"
+        errorLine2="                                                             ~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                        this, generateVideoFileOutputOptions(fileName, extension));"
+        errorLine2="                                                                       ~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                        generateVideoMediaStoreOptions(getContentResolver(), fileName));"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                        generateVideoMediaStoreOptions(getContentResolver(), fileName));"
+        errorLine2="                                                       ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                        generateVideoMediaStoreOptions(getContentResolver(), fileName));"
+        errorLine2="                                                                             ~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        String videoFilePath = getAbsolutePathFromUri(getContentResolver(),"
+        errorLine2="                               ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        String videoFilePath = getAbsolutePathFromUri(getContentResolver(),"
+        errorLine2="                                                      ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                MediaStore.Video.Media.EXTERNAL_CONTENT_URI);"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        if (videoFilePath == null || !createParentFolder(videoFilePath)) {"
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        if (videoFilePath == null || !createParentFolder(videoFilePath)) {"
+        errorLine2="                                                         ~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                        videoFilePath = getAbsolutePathFromUri("
+        errorLine2="                                        ~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                                getApplicationContext().getContentResolver(),"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                                uri"
+        errorLine2="                                ~~~">
+        <location
+            file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+    </issue>
+
+    <issue
+        id="RestrictedApiAndroidX"
         message="TransformationInfo.hasCameraTransform can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
         errorLine1="                                    mHasCameraTransform = transformationInfo.hasCameraTransform();"
         errorLine2="                                                                             ~~~~~~~~~~~~~~~~~~">
@@ -264,34 +462,34 @@
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.canDeviceWriteToMediaStore can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="        if (E2ETestUtil.canDeviceWriteToMediaStore()) {"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        message="FileUtil.canDeviceWriteToMediaStore can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        if (FileUtil.canDeviceWriteToMediaStore()) {"
+        errorLine2="                     ~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="                    E2ETestUtil.generateVideoMediaStoreOptions(this.getContentResolver(),"
-        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                    FileUtil.generateVideoMediaStoreOptions(this.getContentResolver(),"
+        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="                    E2ETestUtil.generateVideoMediaStoreOptions(this.getContentResolver(),"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~~~~~~~">
+        message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                    FileUtil.generateVideoMediaStoreOptions(this.getContentResolver(),"
+        errorLine2="                                                            ~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
         errorLine1="                            videoFileName));"
         errorLine2="                            ~~~~~~~~~~~~~">
         <location
@@ -300,52 +498,52 @@
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="                    E2ETestUtil.generateVideoFileOutputOptions(videoFileName, &quot;mp4&quot;));"
-        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                    FileUtil.generateVideoFileOutputOptions(videoFileName, &quot;mp4&quot;));"
+        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="                    E2ETestUtil.generateVideoFileOutputOptions(videoFileName, &quot;mp4&quot;));"
-        errorLine2="                                                               ~~~~~~~~~~~~~">
+        message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                    FileUtil.generateVideoFileOutputOptions(videoFileName, &quot;mp4&quot;));"
+        errorLine2="                                                            ~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="                    E2ETestUtil.generateVideoFileOutputOptions(videoFileName, &quot;mp4&quot;));"
-        errorLine2="                                                                              ~~~~~">
+        message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="                    FileUtil.generateVideoFileOutputOptions(videoFileName, &quot;mp4&quot;));"
+        errorLine2="                                                                           ~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="        E2ETestUtil.writeTextToExternalFile(information,"
-        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~">
+        message="FileUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        FileUtil.writeTextToExternalFile(information,"
+        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="        E2ETestUtil.writeTextToExternalFile(information,"
-        errorLine2="                                            ~~~~~~~~~~~">
+        message="FileUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        FileUtil.writeTextToExternalFile(information,"
+        errorLine2="                                         ~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        message="FileUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
         errorLine1="                generateFileName(INFO_FILE_PREFIX, false), &quot;txt&quot;);"
         errorLine2="                ~~~~~~~~~~~~~~~~">
         <location
@@ -354,7 +552,7 @@
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        message="FileUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
         errorLine1="                generateFileName(INFO_FILE_PREFIX, false), &quot;txt&quot;);"
         errorLine2="                                                           ~~~~~">
         <location
@@ -363,36 +561,36 @@
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.isFileNameValid can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="        if (!isUnique &amp;&amp; !E2ETestUtil.isFileNameValid(prefix)) {"
-        errorLine2="                                      ~~~~~~~~~~~~~~~">
+        message="FileUtil.isFileNameValid can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        if (!isUnique &amp;&amp; !FileUtil.isFileNameValid(prefix)) {"
+        errorLine2="                                   ~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.isFileNameValid can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="        if (!isUnique &amp;&amp; !E2ETestUtil.isFileNameValid(prefix)) {"
-        errorLine2="                                                      ~~~~~~">
+        message="FileUtil.isFileNameValid can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        if (!isUnique &amp;&amp; !FileUtil.isFileNameValid(prefix)) {"
+        errorLine2="                                                   ~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.isFileNameValid can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="        if (E2ETestUtil.isFileNameValid(prefix)) {"
-        errorLine2="                        ~~~~~~~~~~~~~~~">
+        message="FileUtil.isFileNameValid can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        if (FileUtil.isFileNameValid(prefix)) {"
+        errorLine2="                     ~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
     </issue>
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.isFileNameValid can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
-        errorLine1="        if (E2ETestUtil.isFileNameValid(prefix)) {"
-        errorLine2="                                        ~~~~~~">
+        message="FileUtil.isFileNameValid can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        errorLine1="        if (FileUtil.isFileNameValid(prefix)) {"
+        errorLine2="                                     ~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java"/>
     </issue>
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXServiceTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXServiceTest.kt
new file mode 100644
index 0000000..5ce4624
--- /dev/null
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXServiceTest.kt
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.integration.core
+
+import android.Manifest
+import android.app.ActivityManager
+import android.app.Service
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.Build
+import android.os.IBinder
+import androidx.camera.camera2.Camera2Config
+import androidx.camera.camera2.pipe.integration.CameraPipeConfig
+import androidx.camera.core.CameraSelector.LENS_FACING_BACK
+import androidx.camera.core.CameraXConfig
+import androidx.camera.core.ImageAnalysis
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.UseCase
+import androidx.camera.integration.core.CameraXService.ACTION_BIND_USE_CASES
+import androidx.camera.integration.core.CameraXService.ACTION_START_RECORDING
+import androidx.camera.integration.core.CameraXService.ACTION_STOP_RECORDING
+import androidx.camera.integration.core.CameraXService.ACTION_TAKE_PICTURE
+import androidx.camera.integration.core.CameraXService.EXTRA_IMAGE_ANALYSIS_ENABLED
+import androidx.camera.integration.core.CameraXService.EXTRA_IMAGE_CAPTURE_ENABLED
+import androidx.camera.integration.core.CameraXService.EXTRA_VIDEO_CAPTURE_ENABLED
+import androidx.camera.testing.impl.CameraPipeConfigTestRule
+import androidx.camera.testing.impl.CameraUtil
+import androidx.camera.testing.impl.CameraUtil.hasCameraWithLensFacing
+import androidx.camera.testing.impl.mocks.MockConsumer
+import androidx.camera.testing.impl.mocks.helpers.ArgumentCaptor
+import androidx.camera.testing.impl.mocks.helpers.CallTimes
+import androidx.camera.video.VideoCapture
+import androidx.lifecycle.Lifecycle
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.LargeTest
+import androidx.test.rule.GrantPermissionRule
+import androidx.testutils.LifecycleOwnerUtils
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+import org.junit.After
+import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+class CameraXServiceTest(
+    private val implName: String,
+    private val cameraXConfig: CameraXConfig
+) {
+    @get:Rule
+    val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
+        CameraUtil.PreTestCameraIdList(cameraXConfig)
+    )
+
+    @get:Rule
+    val permissionRule: GrantPermissionRule =
+        GrantPermissionRule.grant(
+            Manifest.permission.WRITE_EXTERNAL_STORAGE,
+            Manifest.permission.RECORD_AUDIO,
+        )
+
+    @get:Rule
+    val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+        active = implName == CameraPipeConfig::class.simpleName,
+    )
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun data() = listOf(
+            arrayOf(Camera2Config::class.simpleName, Camera2Config.defaultConfig()),
+            arrayOf(CameraPipeConfig::class.simpleName, CameraPipeConfig.defaultConfig())
+        )
+    }
+
+    private val context = ApplicationProvider.getApplicationContext<Context>()
+    private val activityManager =
+        context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+    private lateinit var serviceConnection: ServiceConnection
+    private lateinit var service: CameraXService
+
+    @Before
+    fun setUp() = runBlocking {
+        assumeTrue(hasCameraWithLensFacing(LENS_FACING_BACK))
+        assumeFalse(isBackgroundRestricted())
+
+        service = bindService()
+
+        // Ensure service is started.
+        LifecycleOwnerUtils.waitUntilState(service, Lifecycle.State.STARTED)
+    }
+
+    @After
+    fun tearDown() {
+        if (this::service.isInitialized) {
+            service.deleteSavedMediaFiles()
+            context.unbindService(serviceConnection)
+            context.stopService(createServiceIntent())
+
+            // Ensure service is destroyed
+            LifecycleOwnerUtils.waitUntilState(service, Lifecycle.State.DESTROYED)
+        }
+    }
+
+    @Test
+    fun canStartServiceAsForeground() {
+        assertThat(isForegroundService(service)).isTrue()
+    }
+
+    @Test
+    fun canBindUseCases() {
+        // Arrange: set up onUseCaseBound callback.
+        val useCaseCallback = MockConsumer<Collection<UseCase>>()
+        service.setOnUseCaseBoundCallback(useCaseCallback)
+
+        // Act: bind VideoCapture and ImageCapture.
+        context.startService(createServiceIntent(ACTION_BIND_USE_CASES).apply {
+            putExtra(EXTRA_VIDEO_CAPTURE_ENABLED, true)
+            putExtra(EXTRA_IMAGE_CAPTURE_ENABLED, true)
+        })
+
+        // Assert: verify bound UseCases.
+        val captor = ArgumentCaptor<Collection<UseCase>>()
+        useCaseCallback.verifyAcceptCall(Collection::class.java, false, 3000L, CallTimes(1), captor)
+        assertThat(captor.value!!.map { it.javaClass }).containsExactly(
+            VideoCapture::class.java,
+            ImageCapture::class.java
+        )
+
+        // Act: rebind by ImageAnalysis.
+        useCaseCallback.clearAcceptCalls()
+        context.startService(createServiceIntent(ACTION_BIND_USE_CASES).apply {
+            putExtra(EXTRA_IMAGE_ANALYSIS_ENABLED, true)
+        })
+
+        // Assert: verify bound UseCases.
+        useCaseCallback.verifyAcceptCall(Collection::class.java, false, 3000L, CallTimes(1), captor)
+        assertThat(captor.value!!.map { it.javaClass }).containsExactly(
+            ImageAnalysis::class.java,
+        )
+    }
+
+    @Test
+    fun canReceiveAnalysisFrame() {
+        // Arrange.
+        context.startService(createServiceIntent(ACTION_BIND_USE_CASES).apply {
+            putExtra(EXTRA_IMAGE_ANALYSIS_ENABLED, true)
+        })
+
+        // Act.
+        val latch = service.acquireAnalysisFrameCountDownLatch()
+
+        // Assert.
+        assertThat(latch.await(15, TimeUnit.SECONDS)).isTrue()
+    }
+
+    @Test
+    fun canTakePicture() {
+        // Arrange.
+        context.startService(createServiceIntent(ACTION_BIND_USE_CASES).apply {
+            putExtra(EXTRA_IMAGE_CAPTURE_ENABLED, true)
+        })
+
+        // Act.
+        val latch = service.acquireTakePictureCountDownLatch()
+        context.startService(createServiceIntent(ACTION_TAKE_PICTURE))
+
+        // Assert.
+        assertThat(latch.await(15, TimeUnit.SECONDS)).isTrue()
+    }
+
+    @Test
+    fun canRecordVideo() = runBlocking {
+        // Arrange.
+        context.startService(createServiceIntent(ACTION_BIND_USE_CASES).apply {
+            putExtra(EXTRA_VIDEO_CAPTURE_ENABLED, true)
+        })
+
+        // Act.
+        val latch = service.acquireRecordVideoCountDownLatch()
+        context.startService(createServiceIntent(ACTION_START_RECORDING))
+
+        delay(3000L)
+
+        context.startService(createServiceIntent(ACTION_STOP_RECORDING))
+
+        // Assert.
+        assertThat(latch.await(15, TimeUnit.SECONDS)).isTrue()
+    }
+
+    private fun createServiceIntent(action: String? = null) =
+        Intent(context, CameraXService::class.java).apply {
+            action?.let { setAction(it) }
+        }
+
+    private fun isForegroundService(service: Service): Boolean {
+        val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+        @Suppress("DEPRECATION")
+        for (serviceInfo in manager.getRunningServices(Int.MAX_VALUE)) {
+            if (service.javaClass.name == serviceInfo.service.className) {
+                return serviceInfo.foreground
+            }
+        }
+        return false
+    }
+
+    private suspend fun bindService(): CameraXService {
+        val serviceDeferred = CompletableDeferred<CameraXService>()
+        serviceConnection = object : ServiceConnection {
+            override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
+                val binder = service as CameraXService.CameraXServiceBinder
+                serviceDeferred.complete(binder.service)
+            }
+
+            override fun onServiceDisconnected(name: ComponentName?) {
+            }
+        }
+        context.bindService(createServiceIntent(), serviceConnection, Service.BIND_AUTO_CREATE)
+        return withTimeout(3000L) {
+            serviceDeferred.await()
+        }
+    }
+
+    private fun isBackgroundRestricted(): Boolean =
+        if (Build.VERSION.SDK_INT >= 28) activityManager.isBackgroundRestricted else false
+}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
index 94eb4bd..e10ff38 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
@@ -64,6 +64,7 @@
 import androidx.camera.core.impl.utils.CameraOrientationUtil
 import androidx.camera.core.impl.utils.Exif
 import androidx.camera.core.internal.compat.workaround.ExifRotationAvailability
+import androidx.camera.core.internal.compat.workaround.InvalidJpegDataParser
 import androidx.camera.core.resolutionselector.AspectRatioStrategy
 import androidx.camera.core.resolutionselector.ResolutionFilter
 import androidx.camera.core.resolutionselector.ResolutionSelector
@@ -1309,7 +1310,14 @@
         val useCase = builder.build()
         var camera: Camera
         withContext(Dispatchers.Main) {
-            camera = cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, useCase)
+            camera = cameraProvider.bindToLifecycle(
+                fakeLifecycleOwner,
+                BACK_SELECTOR,
+                useCase,
+                Preview.Builder().build().apply {
+                    setSurfaceProvider(SurfaceTextureProvider.createSurfaceTextureProvider())
+                }
+            )
         }
 
         val callback = FakeImageCaptureCallback(capturesCount = 1)
@@ -1341,7 +1349,14 @@
         val useCase = builder.build()
         var camera: Camera
         withContext(Dispatchers.Main) {
-            camera = cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, useCase)
+            camera = cameraProvider.bindToLifecycle(
+                fakeLifecycleOwner,
+                BACK_SELECTOR,
+                useCase,
+                Preview.Builder().build().apply {
+                    setSurfaceProvider(SurfaceTextureProvider.createSurfaceTextureProvider())
+                }
+            )
         }
 
         val saveLocation = temporaryFolder.newFile("test.jpg")
@@ -1365,7 +1380,14 @@
         val useCase = builder.build()
         var camera: Camera
         withContext(Dispatchers.Main) {
-            camera = cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, useCase)
+            camera = cameraProvider.bindToLifecycle(
+                fakeLifecycleOwner,
+                BACK_SELECTOR,
+                useCase,
+                Preview.Builder().build().apply {
+                    setSurfaceProvider(SurfaceTextureProvider.createSurfaceTextureProvider())
+                }
+            )
         }
 
         val callback = FakeImageCaptureCallback(capturesCount = 1)
@@ -1396,7 +1418,14 @@
         val useCase = builder.build()
         var camera: Camera
         withContext(Dispatchers.Main) {
-            camera = cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, useCase)
+            camera = cameraProvider.bindToLifecycle(
+                fakeLifecycleOwner,
+                BACK_SELECTOR,
+                useCase,
+                Preview.Builder().build().apply {
+                    setSurfaceProvider(SurfaceTextureProvider.createSurfaceTextureProvider())
+                }
+            )
         }
 
         val callback = FakeImageCaptureCallback(capturesCount = 1)
@@ -1500,6 +1529,22 @@
         assertThat(imageProperties.format).isEqualTo(ImageFormat.JPEG)
     }
 
+    @Test
+    fun canCaptureImage_whenOnlyImageCaptureBound_withYuvBufferFormat() {
+        val cameraHwLevel = CameraUtil.getCameraCharacteristics(CameraSelector.LENS_FACING_BACK)
+            ?.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
+        assumeTrue(
+            "TODO(b/298138582): Check if MeteringRepeating will need to be added while" +
+                " choosing resolution for ImageCapture",
+            cameraHwLevel != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY &&
+                cameraHwLevel != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+
+        canTakeImages(ImageCapture.Builder().apply {
+            setBufferFormat(ImageFormat.YUV_420_888)
+        })
+    }
+
     private fun getCameraSelectorWithSessionProcessor(
         cameraSelector: CameraSelector,
         sessionProcessor: SessionProcessor
@@ -1758,6 +1803,89 @@
         assertThat(imageProperties.size).isEqualTo(maxHighResolutionOutputSize)
     }
 
+    /**
+     * See b/288828159 for the detailed info of the issue
+     */
+    @Test
+    fun jpegImageZeroPaddingDataDetectionTest(): Unit = runBlocking {
+        val imageCapture = ImageCapture.Builder().build()
+
+        withContext(Dispatchers.Main) {
+            cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, imageCapture)
+        }
+
+        val latch = CountdownDeferred(1)
+        var errors: Exception? = null
+
+        val callback = object : ImageCapture.OnImageCapturedCallback() {
+            override fun onCaptureSuccess(image: ImageProxy) {
+                val planes = image.planes
+                val buffer = planes[0].buffer
+                val data = ByteArray(buffer.capacity())
+                buffer.rewind()
+                buffer[data]
+
+                image.close()
+
+                val invalidJpegDataParser = InvalidJpegDataParser()
+
+                // Only checks the unnecessary zero padding data when the device is not included in
+                // the LargeJpegImageQuirk device list. InvalidJpegDataParser#getValidDataLength()
+                // should have returned the valid data length to avoid the extremely large JPEG
+                // file issue.
+                if (invalidJpegDataParser.getValidDataLength(data) == data.size &&
+                    containsZeroPaddingDataAfterEoi(data)
+                ) {
+                    errors = Exception("UNNECESSARY_JPEG_ZERO_PADDING_DATA_DETECTED!")
+                }
+
+                latch.countDown()
+            }
+
+            override fun onError(exception: ImageCaptureException) {
+                errors = exception
+                latch.countDown()
+            }
+        }
+
+        imageCapture.takePicture(mainExecutor, callback)
+
+        // Wait for the signal that the image has been captured.
+        assertThat(withTimeoutOrNull(CAPTURE_TIMEOUT) {
+            latch.await()
+        }).isNotNull()
+        assertThat(errors).isNull()
+    }
+
+    /**
+     * This util function is only used to detect the unnecessary zero padding data after EOI. It
+     * will directly return false when it fails to parse the JPEG byte array data.
+     */
+    private fun containsZeroPaddingDataAfterEoi(bytes: ByteArray): Boolean {
+        val jfifEoiMarkEndPosition = InvalidJpegDataParser.getJfifEoiMarkEndPosition(bytes)
+
+        // Directly returns false when EOI mark can't be found.
+        if (jfifEoiMarkEndPosition == -1) {
+            return false
+        }
+
+        // Will check 1mb data to know whether unnecessary zero padding data exists or not.
+        // Directly returns false when the data length is long enough
+        val dataLengthToDetect = 1_000_000
+        if (jfifEoiMarkEndPosition + dataLengthToDetect > bytes.size) {
+            return false
+        }
+
+        // Checks that there are at least continuous 1mb of unnecessary zero padding data after EOI
+        for (position in jfifEoiMarkEndPosition..jfifEoiMarkEndPosition + dataLengthToDetect) {
+            if (bytes[position] != 0x00.toByte()) {
+                return false
+            }
+        }
+
+        return true
+    }
+
     private fun createNonRotatedConfiguration(): ImageCaptureConfig {
         // Create a configuration with target rotation that matches the sensor rotation.
         // This assumes a back-facing camera (facing away from screen)
diff --git a/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml b/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
index 425ca58..5ee37e2 100644
--- a/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
+++ b/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
@@ -76,6 +76,18 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <service
+            android:name=".CameraXService"
+            android:exported="true"
+            android:foregroundServiceType="camera|microphone"
+            android:label="CameraX Service">
+            <intent-filter>
+                <action android:name="androidx.camera.integration.core.intent.action.BIND_USE_CASES" />
+                <action android:name="androidx.camera.integration.core.intent.action.TAKE_PICTURE" />
+                <action android:name="androidx.camera.integration.core.intent.action.START_RECORDING" />
+                <action android:name="androidx.camera.integration.core.intent.action.STOP_RECORDING" />
+            </intent-filter>
+        </service>
     </application>
 
     <uses-feature android:glEsVersion="0x00020000" />
@@ -85,4 +97,7 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
 </manifest>
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index c653b5f..b304a9a 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -25,21 +25,25 @@
 import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF;
 import static androidx.camera.core.ImageCapture.FLASH_MODE_ON;
 import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY;
+import static androidx.camera.testing.impl.FileUtil.canDeviceWriteToMediaStore;
+import static androidx.camera.testing.impl.FileUtil.createParentFolder;
+import static androidx.camera.testing.impl.FileUtil.generateVideoFileOutputOptions;
+import static androidx.camera.testing.impl.FileUtil.generateVideoMediaStoreOptions;
+import static androidx.camera.testing.impl.FileUtil.getAbsolutePathFromUri;
 import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_DURATION_LIMIT_REACHED;
 import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_FILE_SIZE_LIMIT_REACHED;
 import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_INSUFFICIENT_STORAGE;
 import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_NONE;
 import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_SOURCE_INACTIVE;
 
+import static java.util.Objects.requireNonNull;
+
 import android.Manifest;
 import android.annotation.SuppressLint;
-import android.content.ContentResolver;
 import android.content.ContentValues;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.database.Cursor;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.display.DisplayManager;
 import android.media.MediaScannerConnection;
@@ -126,8 +130,6 @@
 import androidx.camera.video.VideoCapabilities;
 import androidx.camera.video.VideoCapture;
 import androidx.camera.video.VideoRecordEvent;
-import androidx.camera.video.internal.compat.quirk.DeviceQuirks;
-import androidx.camera.video.internal.compat.quirk.MediaStoreVideoCannotWrite;
 import androidx.core.content.ContextCompat;
 import androidx.core.math.MathUtils;
 import androidx.core.util.Consumer;
@@ -357,6 +359,9 @@
 
         @Override
         public void onSuccess(@Nullable Integer result) {
+            if (result == null) {
+                return;
+            }
             CameraInfo cameraInfo = getCameraInfo();
             if (cameraInfo != null) {
                 ExposureState exposureState = cameraInfo.getExposureState();
@@ -478,7 +483,7 @@
     @VisibleForTesting
     public void resetViewIdlingResource() {
         mPreviewFrameCount.set(0);
-        // Make the view idling resource non-idle, until required framecount achieved.
+        // Make the view idling resource non-idle, until required frame count achieved.
         if (mViewIdlingResource.isIdleNow()) {
             mViewIdlingResource.increment();
         }
@@ -541,6 +546,7 @@
         return mPhotoToggle.isChecked() && cameraInfo != null && cameraInfo.hasFlashUnit();
     }
 
+    @SuppressLint("RestrictedApiAndroidX")
     private boolean isFlashTestSupported(@ImageCapture.FlashMode int flashMode) {
         switch (flashMode) {
             case FLASH_MODE_OFF:
@@ -548,7 +554,8 @@
             case FLASH_MODE_AUTO:
                 CameraInfo cameraInfo = getCameraInfo();
                 if (cameraInfo instanceof CameraInfoInternal) {
-                    Quirks deviceQuirks = DeviceQuirks.getAll();
+                    Quirks deviceQuirks =
+                            androidx.camera.camera2.internal.compat.quirk.DeviceQuirks.getAll();
                     Quirks cameraQuirks = ((CameraInfoInternal) cameraInfo).getCameraQuirks();
                     if (deviceQuirks.contains(CrashWhenTakingPhotoWithAutoFlashAEModeQuirk.class)
                             || cameraQuirks.contains(ImageCaptureFailWithAutoFlashQuirk.class)
@@ -594,14 +601,17 @@
                 case IDLE:
                     createDefaultVideoFolderIfNotExist();
                     final PendingRecording pendingRecording;
-                    if (DeviceQuirks.get(MediaStoreVideoCannotWrite.class) != null) {
-                        // Use FileOutputOption for devices in MediaStoreVideoCannotWrite Quirk.
-                        pendingRecording = getVideoCapture().getOutput().prepareRecording(
-                                this, getNewVideoFileOutputOptions());
-                    } else {
+                    String fileName = "video_" + System.currentTimeMillis();
+                    String extension = "mp4";
+                    if (canDeviceWriteToMediaStore()) {
                         // Use MediaStoreOutputOptions for public share media storage.
                         pendingRecording = getVideoCapture().getOutput().prepareRecording(
-                                this, getNewVideoOutputMediaStoreOptions());
+                                this,
+                                generateVideoMediaStoreOptions(getContentResolver(), fileName));
+                    } else {
+                        // Use FileOutputOption for devices in MediaStoreVideoCannotWrite Quirk.
+                        pendingRecording = getVideoCapture().getOutput().prepareRecording(
+                                this, generateVideoFileOutputOptions(fileName, extension));
                     }
 
                     resetVideoSavedIdlingResource();
@@ -804,32 +814,6 @@
         }
     }
 
-    @NonNull
-    private MediaStoreOutputOptions getNewVideoOutputMediaStoreOptions() {
-        String videoFileName = "video_" + System.currentTimeMillis();
-        ContentValues contentValues = new ContentValues();
-        contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4");
-        contentValues.put(MediaStore.Video.Media.TITLE, videoFileName);
-        contentValues.put(MediaStore.Video.Media.DISPLAY_NAME, videoFileName);
-        contentValues.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
-        contentValues.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
-        return new MediaStoreOutputOptions.Builder(getContentResolver(),
-                MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
-                .setContentValues(contentValues)
-                .build();
-    }
-
-    @NonNull
-    private FileOutputOptions getNewVideoFileOutputOptions() {
-        String videoFileName = "video_" + System.currentTimeMillis() + ".mp4";
-        File videoFolder = Environment.getExternalStoragePublicDirectory(
-                Environment.DIRECTORY_MOVIES);
-        if (!videoFolder.exists() && !videoFolder.mkdirs()) {
-            Log.e(TAG, "Failed to create directory: " + videoFolder);
-        }
-        return new FileOutputOptions.Builder(new File(videoFolder, videoFileName)).build();
-    }
-
     private void updateRecordingStats(@NonNull RecordingStats stats) {
         double durationMs = TimeUnit.NANOSECONDS.toMillis(stats.getRecordedDurationNanos());
         // Show megabytes in International System of Units (SI)
@@ -888,7 +872,7 @@
                                                 Toast.LENGTH_SHORT).show());
                                         if (mSessionImagesUriSet != null) {
                                             mSessionImagesUriSet.add(
-                                                    Objects.requireNonNull(
+                                                    requireNonNull(
                                                             outputFileResults.getSavedUri()));
                                         }
                                     }
@@ -954,7 +938,7 @@
                     Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
                 }
             } catch (IllegalArgumentException e) {
-                Toast.makeText(this, "Failed to swich Camera. Error:" + e.getMessage(),
+                Toast.makeText(this, "Failed to switch Camera. Error:" + e.getMessage(),
                         Toast.LENGTH_SHORT).show();
             }
         });
@@ -1006,8 +990,8 @@
 
     private void setUpTorchButton() {
         mTorchButton.setOnClickListener(v -> {
-            Objects.requireNonNull(getCameraInfo());
-            Objects.requireNonNull(getCameraControl());
+            requireNonNull(getCameraInfo());
+            requireNonNull(getCameraControl());
             Integer torchState = getCameraInfo().getTorchState().getValue();
             boolean toggledState = !Objects.equals(torchState, TorchState.ON);
             Log.d(TAG, "Set camera torch: " + toggledState);
@@ -1027,8 +1011,8 @@
 
     private void setUpEVButton() {
         mPlusEV.setOnClickListener(v -> {
-            Objects.requireNonNull(getCameraInfo());
-            Objects.requireNonNull(getCameraControl());
+            requireNonNull(getCameraInfo());
+            requireNonNull(getCameraControl());
 
             ExposureState exposureState = getCameraInfo().getExposureState();
             Range<Integer> range = exposureState.getExposureCompensationRange();
@@ -1046,8 +1030,8 @@
         });
 
         mDecEV.setOnClickListener(v -> {
-            Objects.requireNonNull(getCameraInfo());
-            Objects.requireNonNull(getCameraControl());
+            requireNonNull(getCameraInfo());
+            requireNonNull(getCameraControl());
 
             ExposureState exposureState = getCameraInfo().getExposureState();
             Range<Integer> range = exposureState.getExposureCompensationRange();
@@ -1118,12 +1102,12 @@
                 ViewGroup.LayoutParams lp = viewFinderStub.getLayoutParams();
                 if (orientation == Configuration.ORIENTATION_PORTRAIT) {
                     lp.width = displayMetrics.widthPixels;
-                    lp.height = (int) (displayMetrics.widthPixels / ratio.getDenominator()
-                            * ratio.getNumerator());
+                    lp.height = displayMetrics.widthPixels / ratio.getDenominator()
+                            * ratio.getNumerator();
                 } else {
                     lp.height = displayMetrics.heightPixels;
-                    lp.width = (int) (displayMetrics.heightPixels / ratio.getDenominator()
-                            * ratio.getNumerator());
+                    lp.width = displayMetrics.heightPixels / ratio.getDenominator()
+                            * ratio.getNumerator();
                 }
                 viewFinderStub.setLayoutParams(lp);
             }
@@ -1327,7 +1311,7 @@
         };
 
         DisplayManager dpyMgr =
-                Objects.requireNonNull((DisplayManager) getSystemService(Context.DISPLAY_SERVICE));
+                requireNonNull(ContextCompat.getSystemService(this, DisplayManager.class));
         dpyMgr.registerDisplayListener(mDisplayListener, new Handler(Looper.getMainLooper()));
 
         StrictMode.VmPolicy vmPolicy =
@@ -1444,7 +1428,7 @@
     public void onDestroy() {
         super.onDestroy();
         DisplayManager dpyMgr =
-                Objects.requireNonNull((DisplayManager) getSystemService(Context.DISPLAY_SERVICE));
+                requireNonNull(ContextCompat.getSystemService(this, DisplayManager.class));
         dpyMgr.unregisterDisplayListener(mDisplayListener);
         mPreviewRenderer.shutdown();
         mImageCaptureExecutorService.shutdown();
@@ -1575,12 +1559,9 @@
         // Remove ImageAnalysis to check whether the new use cases combination can be supported.
         if (mAnalysisToggle.isChecked()) {
             mAnalysisToggle.setChecked(false);
-            if (isCheckedUseCasesCombinationSupported()) {
-                return;
-            }
+            // No need to do further use case combination check since Preview + ImageCapture
+            // should be always supported.
         }
-
-        // Preview + ImageCapture should be always supported.
     }
 
     /**
@@ -1685,7 +1666,7 @@
                             new ActivityResultContracts.RequestMultiplePermissions(),
                             result -> {
                                 for (String permission : REQUIRED_PERMISSIONS) {
-                                    if (!Objects.requireNonNull(result.get(permission))) {
+                                    if (!requireNonNull(result.get(permission))) {
                                         Toast.makeText(getApplicationContext(),
                                                         "Camera permission denied.",
                                                         Toast.LENGTH_SHORT)
@@ -1718,10 +1699,8 @@
     void createDefaultPictureFolderIfNotExist() {
         File pictureFolder = Environment.getExternalStoragePublicDirectory(
                 Environment.DIRECTORY_PICTURES);
-        if (!pictureFolder.exists()) {
-            if (!pictureFolder.mkdir()) {
-                Log.e(TAG, "Failed to create directory: " + pictureFolder);
-            }
+        if (createParentFolder(pictureFolder)) {
+            Log.e(TAG, "Failed to create directory: " + pictureFolder);
         }
     }
 
@@ -1730,17 +1709,8 @@
         String videoFilePath =
                 getAbsolutePathFromUri(getApplicationContext().getContentResolver(),
                         MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
-
-        // If cannot get the video path, just skip checking and create folder.
-        if (videoFilePath == null) {
-            return;
-        }
-        File videoFile = new File(videoFilePath);
-
-        if (videoFile.getParentFile() != null && !videoFile.getParentFile().exists()) {
-            if (!videoFile.getParentFile().mkdir()) {
-                Log.e(TAG, "Failed to create directory: " + videoFile);
-            }
+        if (videoFilePath == null || !createParentFolder(videoFilePath)) {
+            Log.e(TAG, "Failed to create parent directory for: " + videoFilePath);
         }
     }
 
@@ -1776,13 +1746,14 @@
     ScaleGestureDetector.SimpleOnScaleGestureListener mScaleGestureListener =
             new ScaleGestureDetector.SimpleOnScaleGestureListener() {
                 @Override
-                public boolean onScale(ScaleGestureDetector detector) {
+                public boolean onScale(@NonNull ScaleGestureDetector detector) {
                     if (mCamera == null) {
                         return true;
                     }
 
                     CameraInfo cameraInfo = mCamera.getCameraInfo();
-                    float newZoom = cameraInfo.getZoomState().getValue().getZoomRatio()
+                    float newZoom =
+                            requireNonNull(cameraInfo.getZoomState().getValue()).getZoomRatio()
                             * detector.getScaleFactor();
                     setZoomRatio(newZoom);
                     return true;
@@ -1792,7 +1763,7 @@
     GestureDetector.OnGestureListener onTapGestureListener =
             new GestureDetector.SimpleOnGestureListener() {
                 @Override
-                public boolean onSingleTapUp(MotionEvent e) {
+                public boolean onSingleTapUp(@NonNull MotionEvent e) {
                     if (mCamera == null) {
                         return false;
                     }
@@ -1832,7 +1803,8 @@
 
         mZoomSeekBar.setMax(MAX_SEEKBAR_VALUE);
         mZoomSeekBar.setProgress(
-                (int) (cameraInfo.getZoomState().getValue().getLinearZoom() * MAX_SEEKBAR_VALUE));
+                (int) (requireNonNull(cameraInfo.getZoomState().getValue()).getLinearZoom()
+                        * MAX_SEEKBAR_VALUE));
         mZoomSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
             @Override
             public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
@@ -1882,7 +1854,7 @@
     private boolean is2XZoomSupported() {
         CameraInfo cameraInfo = getCameraInfo();
         return cameraInfo != null
-                && cameraInfo.getZoomState().getValue().getMaxZoomRatio() >= 2.0f;
+                && requireNonNull(cameraInfo.getZoomState().getValue()).getMaxZoomRatio() >= 2.0f;
     }
 
     private void setUpZoomButton() {
@@ -1899,7 +1871,7 @@
         CameraInfo cameraInfo = mCamera.getCameraInfo();
         CameraControl cameraControl = mCamera.getCameraControl();
         float clampedNewZoom = MathUtils.clamp(newZoom,
-                cameraInfo.getZoomState().getValue().getMinZoomRatio(),
+                requireNonNull(cameraInfo.getZoomState().getValue()).getMinZoomRatio(),
                 cameraInfo.getZoomState().getValue().getMaxZoomRatio());
 
         Log.d(TAG, "setZoomRatio ratio: " + clampedNewZoom);
@@ -1930,34 +1902,6 @@
         });
     }
 
-    /** Gets the absolute path from a Uri. */
-    @Nullable
-    public String getAbsolutePathFromUri(@NonNull ContentResolver resolver,
-            @NonNull Uri contentUri) {
-        Cursor cursor = null;
-        try {
-            // We should include in any Media collections.
-            String[] proj;
-            int columnIndex;
-            // MediaStore.Video.Media.DATA was deprecated in API level 29.
-            proj = new String[]{MediaStore.Video.Media.DATA};
-            cursor = resolver.query(contentUri, proj, null, null, null);
-            columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA);
-
-            cursor.moveToFirst();
-            return cursor.getString(columnIndex);
-        } catch (RuntimeException e) {
-            Log.e(TAG, String.format(
-                    "Failed in getting absolute path for Uri %s with Exception %s",
-                    contentUri, e));
-            return "";
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-    }
-
     private class SessionMediaUriSet {
         private final Set<Uri> mSessionMediaUris;
 
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXService.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXService.java
new file mode 100644
index 0000000..ac8e9cf
--- /dev/null
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXService.java
@@ -0,0 +1,482 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.integration.core;
+
+import static androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA;
+import static androidx.camera.testing.impl.FileUtil.canDeviceWriteToMediaStore;
+import static androidx.camera.testing.impl.FileUtil.createParentFolder;
+import static androidx.camera.testing.impl.FileUtil.generateVideoFileOutputOptions;
+import static androidx.camera.testing.impl.FileUtil.generateVideoMediaStoreOptions;
+import static androidx.camera.testing.impl.FileUtil.getAbsolutePathFromUri;
+import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_DURATION_LIMIT_REACHED;
+import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_FILE_SIZE_LIMIT_REACHED;
+import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_INSUFFICIENT_STORAGE;
+import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_NONE;
+import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_SOURCE_INACTIVE;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.media.MediaScannerConnection;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.provider.MediaStore;
+import android.util.Log;
+
+import androidx.annotation.DoNotInline;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
+import androidx.camera.core.ImageAnalysis;
+import androidx.camera.core.ImageCapture;
+import androidx.camera.core.ImageCaptureException;
+import androidx.camera.core.UseCase;
+import androidx.camera.core.UseCaseGroup;
+import androidx.camera.lifecycle.ProcessCameraProvider;
+import androidx.camera.video.FileOutputOptions;
+import androidx.camera.video.MediaStoreOutputOptions;
+import androidx.camera.video.OutputOptions;
+import androidx.camera.video.PendingRecording;
+import androidx.camera.video.Recorder;
+import androidx.camera.video.Recording;
+import androidx.camera.video.VideoCapture;
+import androidx.camera.video.VideoRecordEvent;
+import androidx.core.app.NotificationCompat;
+import androidx.core.content.ContextCompat;
+import androidx.core.util.Consumer;
+import androidx.lifecycle.LifecycleService;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.io.File;
+import java.text.Format;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * A service used to test background UseCases binding and camera operations.
+ */
+public class CameraXService extends LifecycleService {
+    private static final String TAG = "CameraXService";
+    private static final int NOTIFICATION_ID = 1;
+    private static final String CHANNEL_ID_SERVICE_INFO = "channel_service_info";
+
+    // Actions
+    public static final String ACTION_BIND_USE_CASES =
+            "androidx.camera.integration.core.intent.action.BIND_USE_CASES";
+    public static final String ACTION_TAKE_PICTURE =
+            "androidx.camera.integration.core.intent.action.TAKE_PICTURE";
+    public static final String ACTION_START_RECORDING =
+            "androidx.camera.integration.core.intent.action.START_RECORDING";
+    public static final String ACTION_STOP_RECORDING =
+            "androidx.camera.integration.core.intent.action.STOP_RECORDING";
+
+    // Extras
+    public static final String EXTRA_VIDEO_CAPTURE_ENABLED = "EXTRA_VIDEO_CAPTURE_ENABLED";
+    public static final String EXTRA_IMAGE_CAPTURE_ENABLED = "EXTRA_IMAGE_CAPTURE_ENABLED";
+    public static final String EXTRA_IMAGE_ANALYSIS_ENABLED = "EXTRA_IMAGE_ANALYSIS_ENABLED";
+
+    private final IBinder mBinder = new CameraXServiceBinder();
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //                          Members only accessed on main thread                              //
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    private final Map<Class<?>, UseCase> mBoundUseCases = new HashMap<>();
+    @Nullable
+    private Recording mActiveRecording;
+    //--------------------------------------------------------------------------------------------//
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //                                   Members for testing                                      //
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    private final Set<Uri> mSavedMediaUri = new HashSet<>();
+
+    @Nullable
+    private Consumer<Collection<UseCase>> mOnUseCaseBoundCallback;
+    @Nullable
+    private CountDownLatch mAnalysisFrameLatch;
+    @Nullable
+    private CountDownLatch mTakePictureLatch;
+    @Nullable
+    private CountDownLatch mRecordVideoLatch;
+    //--------------------------------------------------------------------------------------------//
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        makeForeground();
+    }
+
+    @Nullable
+    @Override
+    public IBinder onBind(@NonNull Intent intent) {
+        super.onBind(intent);
+        return mBinder;
+    }
+
+    @Override
+    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
+        if (intent != null) {
+            String action = intent.getAction();
+            Log.d(TAG, "onStartCommand: action = " + action + ", extras = " + intent.getExtras());
+            if (ACTION_BIND_USE_CASES.equals(action)) {
+                bindToLifecycle(intent);
+            } else if (ACTION_TAKE_PICTURE.equals(action)) {
+                takePicture();
+            } else if (ACTION_START_RECORDING.equals(action)) {
+                startRecording();
+            } else if (ACTION_STOP_RECORDING.equals(action)) {
+                stopRecording();
+            }
+        }
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    private void makeForeground() {
+        createNotificationChannel();
+        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this,
+                CHANNEL_ID_SERVICE_INFO)
+                .setSmallIcon(android.R.drawable.ic_menu_camera)
+                .setStyle(new NotificationCompat.DecoratedCustomViewStyle());
+        startForeground(NOTIFICATION_ID, notificationBuilder.build());
+    }
+
+    private void bindToLifecycle(@NonNull Intent intent) {
+        ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
+                ProcessCameraProvider.getInstance(this);
+        ProcessCameraProvider cameraProvider;
+        try {
+            cameraProvider = cameraProviderFuture.get();
+        } catch (ExecutionException | InterruptedException e) {
+            throw new IllegalStateException(e);
+        }
+        cameraProvider.unbindAll();
+        mBoundUseCases.clear();
+        UseCaseGroup useCaseGroup = resolveUseCaseGroup(intent);
+        List<UseCase> boundUseCases = Collections.emptyList();
+        if (useCaseGroup != null) {
+            try {
+                cameraProvider.bindToLifecycle(this, DEFAULT_BACK_CAMERA, useCaseGroup);
+                boundUseCases = useCaseGroup.getUseCases();
+            } catch (IllegalArgumentException e) {
+                Log.w(TAG, "Failed to bind by " + e, e);
+            }
+        }
+        Log.d(TAG, "Bound UseCases: " + boundUseCases);
+        for (UseCase boundUseCase : boundUseCases) {
+            mBoundUseCases.put(boundUseCase.getClass(), boundUseCase);
+        }
+        if (mOnUseCaseBoundCallback != null) {
+            mOnUseCaseBoundCallback.accept(boundUseCases);
+        }
+    }
+
+    @Nullable
+    private UseCaseGroup resolveUseCaseGroup(@NonNull Intent intent) {
+        boolean hasUseCase = false;
+        UseCaseGroup.Builder useCaseGroupBuilder = new UseCaseGroup.Builder();
+
+        if (intent.getBooleanExtra(EXTRA_VIDEO_CAPTURE_ENABLED, false)) {
+            Recorder recorder = new Recorder.Builder().build();
+            VideoCapture<?> videoCapture = new VideoCapture.Builder<>(recorder).build();
+            useCaseGroupBuilder.addUseCase(videoCapture);
+            hasUseCase = true;
+        }
+        if (intent.getBooleanExtra(EXTRA_IMAGE_CAPTURE_ENABLED, false)) {
+            ImageCapture imageCapture = new ImageCapture.Builder().build();
+            useCaseGroupBuilder.addUseCase(imageCapture);
+            hasUseCase = true;
+        }
+        if (intent.getBooleanExtra(EXTRA_IMAGE_ANALYSIS_ENABLED, false)) {
+            ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
+            imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this), mAnalyzer);
+            useCaseGroupBuilder.addUseCase(imageAnalysis);
+            hasUseCase = true;
+        }
+
+        return hasUseCase ? useCaseGroupBuilder.build() : null;
+    }
+
+    private void createNotificationChannel() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            NotificationChannel serviceChannel = Api26Impl.newNotificationChannel(
+                    CHANNEL_ID_SERVICE_INFO,
+                    getString(R.string.camerax_service),
+                    NotificationManager.IMPORTANCE_DEFAULT
+            );
+            Api26Impl.createNotificationChannel(getNotificationManager(), serviceChannel);
+        }
+    }
+
+    @NonNull
+    private NotificationManager getNotificationManager() {
+        return checkNotNull(ContextCompat.getSystemService(this, NotificationManager.class));
+    }
+
+    @Nullable
+    private ImageCapture getImageCapture() {
+        return (ImageCapture) mBoundUseCases.get(ImageCapture.class);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Nullable
+    private VideoCapture<Recorder> getVideoCapture() {
+        return (VideoCapture<Recorder>) mBoundUseCases.get(VideoCapture.class);
+    }
+
+    private void takePicture() {
+        ImageCapture imageCapture = getImageCapture();
+        if (imageCapture == null) {
+            Log.w(TAG, "ImageCapture is not bound.");
+            return;
+        }
+        createDefaultPictureFolderIfNotExist();
+        Format formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US);
+        String fileName = "ServiceTestApp-" + formatter.format(Calendar.getInstance().getTime())
+                + ".jpg";
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
+        contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
+        ImageCapture.OutputFileOptions outputFileOptions =
+                new ImageCapture.OutputFileOptions.Builder(
+                        getContentResolver(),
+                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                        contentValues).build();
+        long startTimeMs = SystemClock.elapsedRealtime();
+        imageCapture.takePicture(outputFileOptions,
+                ContextCompat.getMainExecutor(this),
+                new ImageCapture.OnImageSavedCallback() {
+                    @Override
+                    public void onImageSaved(
+                            @NonNull ImageCapture.OutputFileResults outputFileResults) {
+                        long durationMs = SystemClock.elapsedRealtime() - startTimeMs;
+                        Log.d(TAG, "Saved image " + outputFileResults.getSavedUri()
+                                + "  (" + durationMs + " ms)");
+                        mSavedMediaUri.add(outputFileResults.getSavedUri());
+                        if (mTakePictureLatch != null) {
+                            mTakePictureLatch.countDown();
+                        }
+                    }
+
+                    @Override
+                    public void onError(@NonNull ImageCaptureException exception) {
+                        Log.e(TAG, "Failed to save image by " + exception.getImageCaptureError(),
+                                exception);
+                    }
+                });
+    }
+
+    private void createDefaultPictureFolderIfNotExist() {
+        File pictureFolder = Environment.getExternalStoragePublicDirectory(
+                Environment.DIRECTORY_PICTURES);
+        if (!createParentFolder(pictureFolder)) {
+            Log.e(TAG, "Failed to create directory: " + pictureFolder);
+        }
+    }
+
+    private void startRecording() {
+        VideoCapture<Recorder> videoCapture = getVideoCapture();
+        if (videoCapture == null) {
+            Log.w(TAG, "VideoCapture is not bound.");
+            return;
+        }
+
+        createDefaultVideoFolderIfNotExist();
+        if (mActiveRecording == null) {
+            PendingRecording pendingRecording;
+            String fileName = "video_" + System.currentTimeMillis();
+            String extension = "mp4";
+            if (canDeviceWriteToMediaStore()) {
+                // Use MediaStoreOutputOptions for public share media storage.
+                pendingRecording = getVideoCapture().getOutput().prepareRecording(
+                        this,
+                        generateVideoMediaStoreOptions(getContentResolver(), fileName));
+            } else {
+                // Use FileOutputOption for devices in MediaStoreVideoCannotWrite Quirk.
+                pendingRecording = getVideoCapture().getOutput().prepareRecording(
+                        this, generateVideoFileOutputOptions(fileName, extension));
+            }
+            //noinspection MissingPermission
+            mActiveRecording = pendingRecording
+                    .withAudioEnabled()
+                    .start(ContextCompat.getMainExecutor(this), mRecordingListener);
+        } else {
+            Log.e(TAG, "It should stop the active recording before start a new one.");
+        }
+    }
+
+    private void stopRecording() {
+        if (mActiveRecording != null) {
+            mActiveRecording.stop();
+            mActiveRecording = null;
+        }
+    }
+
+    private void createDefaultVideoFolderIfNotExist() {
+        String videoFilePath = getAbsolutePathFromUri(getContentResolver(),
+                MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
+        if (videoFilePath == null || !createParentFolder(videoFilePath)) {
+            Log.e(TAG, "Failed to create parent directory for: " + videoFilePath);
+        }
+    }
+
+    private final ImageAnalysis.Analyzer mAnalyzer = image -> {
+        if (mAnalysisFrameLatch != null) {
+            mAnalysisFrameLatch.countDown();
+        }
+        image.close();
+    };
+
+    private final Consumer<VideoRecordEvent> mRecordingListener = event -> {
+        if (event instanceof VideoRecordEvent.Finalize) {
+            VideoRecordEvent.Finalize finalize = (VideoRecordEvent.Finalize) event;
+
+            switch (finalize.getError()) {
+                case ERROR_NONE:
+                case ERROR_FILE_SIZE_LIMIT_REACHED:
+                case ERROR_DURATION_LIMIT_REACHED:
+                case ERROR_INSUFFICIENT_STORAGE:
+                case ERROR_SOURCE_INACTIVE:
+                    Uri uri = finalize.getOutputResults().getOutputUri();
+                    OutputOptions outputOptions = finalize.getOutputOptions();
+                    String msg;
+                    String videoFilePath;
+                    if (outputOptions instanceof MediaStoreOutputOptions) {
+                        msg = "Saved video " + uri;
+                        videoFilePath = getAbsolutePathFromUri(
+                                getApplicationContext().getContentResolver(),
+                                uri
+                        );
+                    } else if (outputOptions instanceof FileOutputOptions) {
+                        videoFilePath = ((FileOutputOptions) outputOptions).getFile().getPath();
+                        MediaScannerConnection.scanFile(this,
+                                new String[]{videoFilePath}, null,
+                                (path, uri1) -> Log.i(TAG, "Scanned " + path + " -> uri= " + uri1));
+                        msg = "Saved video " + videoFilePath;
+                    } else {
+                        throw new AssertionError("Unknown or unsupported OutputOptions type: "
+                                + outputOptions.getClass().getSimpleName());
+                    }
+                    // The video file path is used in tracing e2e test log. Don't remove it.
+                    Log.d(TAG, "Saved video file: " + videoFilePath);
+
+                    if (finalize.getError() != ERROR_NONE) {
+                        msg += " with code (" + finalize.getError() + ")";
+                    }
+                    Log.d(TAG, msg, finalize.getCause());
+
+                    mSavedMediaUri.add(uri);
+                    if (mRecordVideoLatch != null) {
+                        mRecordVideoLatch.countDown();
+                    }
+                    break;
+                default:
+                    String errMsg = "Video capture failed by (" + finalize.getError() + "): "
+                            + finalize.getCause();
+                    Log.e(TAG, errMsg, finalize.getCause());
+            }
+            mActiveRecording = null;
+        }
+    };
+
+    @RequiresApi(26)
+    static class Api26Impl {
+
+        private Api26Impl() {
+        }
+
+        /** @noinspection SameParameterValue */
+        @DoNotInline
+        @NonNull
+        static NotificationChannel newNotificationChannel(@NonNull String id,
+                @NonNull CharSequence name, int importance) {
+            return new NotificationChannel(id, name, importance);
+        }
+
+        @DoNotInline
+        static void createNotificationChannel(@NonNull NotificationManager manager,
+                @NonNull NotificationChannel channel) {
+            manager.createNotificationChannel(channel);
+        }
+    }
+
+    @VisibleForTesting
+    void setOnUseCaseBoundCallback(@NonNull Consumer<Collection<UseCase>> callback) {
+        mOnUseCaseBoundCallback = callback;
+    }
+
+    @VisibleForTesting
+    @NonNull
+    CountDownLatch acquireAnalysisFrameCountDownLatch() {
+        mAnalysisFrameLatch = new CountDownLatch(3);
+        return mAnalysisFrameLatch;
+    }
+
+    @VisibleForTesting
+    @NonNull
+    CountDownLatch acquireTakePictureCountDownLatch() {
+        mTakePictureLatch = new CountDownLatch(1);
+        return mTakePictureLatch;
+    }
+
+    @VisibleForTesting
+    @NonNull
+    CountDownLatch acquireRecordVideoCountDownLatch() {
+        mRecordVideoLatch = new CountDownLatch(1);
+        return mRecordVideoLatch;
+    }
+
+    @VisibleForTesting
+    void deleteSavedMediaFiles() {
+        deleteUriSet(mSavedMediaUri);
+    }
+
+    private void deleteUriSet(@NonNull Set<Uri> uriSet) {
+        for (Uri uri : uriSet) {
+            try {
+                getContentResolver().delete(uri, null, null);
+            } catch (RuntimeException e) {
+                Log.w(TAG, "Unable to delete uri: " + uri, e);
+            }
+        }
+    }
+
+    class CameraXServiceBinder extends Binder {
+        @NonNull
+        CameraXService getService() {
+            return CameraXService.this;
+        }
+    }
+}
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXViewModel.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXViewModel.java
index 61a1f09..620f7e0 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXViewModel.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXViewModel.java
@@ -107,7 +107,7 @@
     @OptIn(markerClass = ExperimentalCameraProviderConfiguration.class)
     @MainThread
     public static boolean isCameraProviderUnInitializedOrSameAsParameter(
-            @NonNull String cameraImplementation) {
+            @Nullable String cameraImplementation) {
 
         if (sConfiguredCameraXCameraImplementation == null) {
             return true;
@@ -116,11 +116,7 @@
                 sConfiguredCameraXCameraImplementation);
         cameraImplementation = getCameraProviderName(cameraImplementation);
 
-        if (currentCameraProvider.equals(cameraImplementation)) {
-            return true;
-        }
-
-        return false;
+        return currentCameraProvider.equals(cameraImplementation);
     }
 
     /**
@@ -129,7 +125,7 @@
      */
     @OptIn(markerClass = ExperimentalCameraProviderConfiguration.class)
     @MainThread
-    private static String getCameraProviderName(String mCameraProvider) {
+    private static String getCameraProviderName(@Nullable String mCameraProvider) {
         if (mCameraProvider == null) {
             mCameraProvider = CAMERA2_IMPLEMENTATION_OPTION;
         }
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java
index c775b3d..976b5f3 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/VideoCameraSwitchingActivity.java
@@ -33,7 +33,7 @@
 import androidx.camera.core.Logger;
 import androidx.camera.core.Preview;
 import androidx.camera.lifecycle.ProcessCameraProvider;
-import androidx.camera.testing.impl.E2ETestUtil;
+import androidx.camera.testing.impl.FileUtil;
 import androidx.camera.video.ExperimentalPersistentRecording;
 import androidx.camera.video.PendingRecording;
 import androidx.camera.video.Recorder;
@@ -232,14 +232,14 @@
 
         final String videoFileName = generateFileName(VIDEO_FILE_PREFIX, true);
         final PendingRecording pendingRecording;
-        if (E2ETestUtil.canDeviceWriteToMediaStore()) {
+        if (FileUtil.canDeviceWriteToMediaStore()) {
             // Use MediaStoreOutputOptions for public share media storage.
             pendingRecording = mVideoCapture.getOutput().prepareRecording(this,
-                    E2ETestUtil.generateVideoMediaStoreOptions(this.getContentResolver(),
+                    FileUtil.generateVideoMediaStoreOptions(this.getContentResolver(),
                             videoFileName));
         } else {
             pendingRecording = mVideoCapture.getOutput().prepareRecording(this,
-                    E2ETestUtil.generateVideoFileOutputOptions(videoFileName, "mp4"));
+                    FileUtil.generateVideoFileOutputOptions(videoFileName, "mp4"));
         }
         mRecording = pendingRecording
                 .asPersistentRecording() // Perform the recording as a persistent recording.
@@ -274,17 +274,17 @@
 
     private void exportTestInformation() {
         String information = KEY_DEVICE_ORIENTATION + ": " + mDeviceOrientation;
-        E2ETestUtil.writeTextToExternalFile(information,
+        FileUtil.writeTextToExternalFile(information,
                 generateFileName(INFO_FILE_PREFIX, false), "txt");
     }
 
     @NonNull
     private String generateFileName(@Nullable String prefix, boolean isUnique) {
-        if (!isUnique && !E2ETestUtil.isFileNameValid(prefix)) {
+        if (!isUnique && !FileUtil.isFileNameValid(prefix)) {
             throw new IllegalArgumentException("Invalid arguments for generating file name.");
         }
         StringBuilder fileName = new StringBuilder();
-        if (E2ETestUtil.isFileNameValid(prefix)) {
+        if (FileUtil.isFileNameValid(prefix)) {
             fileName.append(prefix);
             if (isUnique) {
                 fileName.append("_");
diff --git a/camera/integration-tests/coretestapp/src/main/res/values/donottranslate-strings.xml b/camera/integration-tests/coretestapp/src/main/res/values/donottranslate-strings.xml
index e835bfa..de7293e 100644
--- a/camera/integration-tests/coretestapp/src/main/res/values/donottranslate-strings.xml
+++ b/camera/integration-tests/coretestapp/src/main/res/values/donottranslate-strings.xml
@@ -16,6 +16,7 @@
   -->
 
 <resources>
+    <string name="camerax_service">CameraX Service</string>
     <string name="fps_counter_template">%1$s fps</string>
     <string name="toggle_preview_on">preview\non</string>
     <string name="toggle_preview_off">preview\noff</string>
diff --git a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPagerActivityTest.kt b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPagerActivityTest.kt
index e3dc0e9..49a518d 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPagerActivityTest.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/viewpager/ViewPagerActivityTest.kt
@@ -39,7 +39,6 @@
 import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
 import androidx.test.espresso.matcher.ViewMatchers.withId
 import androidx.test.espresso.matcher.ViewMatchers.withText
-import androidx.test.filters.FlakyTest
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
@@ -146,7 +145,6 @@
     }
 
     // The test makes sure the TextureView surface texture keeps the same after switch.
-    @FlakyTest(bugId = 230873000)
     @Test
     fun testPreviewViewUpdateAfterSwitch() {
         launchActivity(lensFacing, cameraXConfig).use { scenario ->
diff --git a/camera/integration-tests/viewtestapp/lint-baseline.xml b/camera/integration-tests/viewtestapp/lint-baseline.xml
index acea7110..cfacbf5 100644
--- a/camera/integration-tests/viewtestapp/lint-baseline.xml
+++ b/camera/integration-tests/viewtestapp/lint-baseline.xml
@@ -264,7 +264,7 @@
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.canDeviceWriteToMediaStore can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        message="FileUtil.canDeviceWriteToMediaStore can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
         errorLine1="        return if (canDeviceWriteToMediaStore()) {"
         errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -273,7 +273,7 @@
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
         errorLine1="                generateVideoMediaStoreOptions(context.contentResolver, fileName)"
         errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -282,7 +282,7 @@
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
         errorLine1="                generateVideoMediaStoreOptions(context.contentResolver, fileName)"
         errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -291,7 +291,7 @@
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
         errorLine1="                generateVideoMediaStoreOptions(context.contentResolver, fileName)"
         errorLine2="                                                                        ~~~~~~~~">
         <location
@@ -300,7 +300,7 @@
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
         errorLine1="            recorder.prepareRecording(context, generateVideoFileOutputOptions(fileName))"
         errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -309,7 +309,7 @@
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
         errorLine1="            recorder.prepareRecording(context, generateVideoFileOutputOptions(fileName))"
         errorLine2="                                                                              ~~~~~~~~">
         <location
@@ -318,7 +318,7 @@
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        message="FileUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
         errorLine1="        writeTextToExternalFile(information, fileName)"
         errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -327,7 +327,7 @@
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        message="FileUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
         errorLine1="        writeTextToExternalFile(information, fileName)"
         errorLine2="                                ~~~~~~~~~~~">
         <location
@@ -336,7 +336,7 @@
 
     <issue
         id="RestrictedApiAndroidX"
-        message="E2ETestUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+        message="FileUtil.writeTextToExternalFile can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
         errorLine1="        writeTextToExternalFile(information, fileName)"
         errorLine2="                                             ~~~~~~~~">
         <location
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/StreamSharingActivity.kt b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/StreamSharingActivity.kt
index a593ec0..6577ce6 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/StreamSharingActivity.kt
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/StreamSharingActivity.kt
@@ -33,10 +33,10 @@
 import androidx.camera.core.UseCase
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.lifecycle.ProcessCameraProvider
-import androidx.camera.testing.impl.E2ETestUtil.canDeviceWriteToMediaStore
-import androidx.camera.testing.impl.E2ETestUtil.generateVideoFileOutputOptions
-import androidx.camera.testing.impl.E2ETestUtil.generateVideoMediaStoreOptions
-import androidx.camera.testing.impl.E2ETestUtil.writeTextToExternalFile
+import androidx.camera.testing.impl.FileUtil.canDeviceWriteToMediaStore
+import androidx.camera.testing.impl.FileUtil.generateVideoFileOutputOptions
+import androidx.camera.testing.impl.FileUtil.generateVideoMediaStoreOptions
+import androidx.camera.testing.impl.FileUtil.writeTextToExternalFile
 import androidx.camera.video.PendingRecording
 import androidx.camera.video.Recorder
 import androidx.camera.video.Recording
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/activity/BaseCarAppActivity.java b/car/app/app-automotive/src/main/java/androidx/car/app/activity/BaseCarAppActivity.java
index 4de2e3a..cb737f6 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/activity/BaseCarAppActivity.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/activity/BaseCarAppActivity.java
@@ -135,11 +135,8 @@
                     // cause a mismatch between the insets applied to the content on the hosts side
                     // vs. the actual visible window available on the client side.
                     Insets insets;
-                    // Android U+ (SDK 34+) introduced SYSTEM_OVERLAYS insets, which we pass to the
-                    // host to adjust padding.
-                    if (Build.VERSION.SDK_INT >= 34) {
-                        // TODO(b/287700349): Add tests once Robolectric supports SDK 34.
-                        insets = Api34Impl.getInsets(windowInsets);
+                    if (Build.VERSION.SDK_INT >= 30) {
+                        insets = Api30Impl.getInsets(windowInsets);
                     } else {
                         insets = WindowInsetsCompat.toWindowInsetsCompat(windowInsets)
                                 .getInsets(WindowInsetsCompat.Type.systemBars()
@@ -252,24 +249,17 @@
                 }
             };
 
-    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-    private static class Api34Impl {
-
-        private Api34Impl() {
-        }
-
-        static Insets getInsets(WindowInsets windowInsets) {
-            return windowInsets.getInsets(WindowInsets.Type.systemBars() | WindowInsets.Type.ime()
-                    | WindowInsets.Type.systemOverlays());
-        }
-    }
-
     @RequiresApi(Build.VERSION_CODES.R)
     private static class Api30Impl {
         private Api30Impl() {
         }
 
         @DoNotInline
+        static Insets getInsets(WindowInsets windowInsets) {
+            return windowInsets.getInsets(WindowInsets.Type.systemBars() | WindowInsets.Type.ime());
+        }
+
+        @DoNotInline
         static WindowInsets getDecorViewInsets(WindowInsets insets) {
             return new WindowInsets.Builder(insets).setInsets(
                     WindowInsets.Type.displayCutout(), Insets.NONE).build();
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppActivityTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
index f371f31..a666d03 100644
--- a/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
@@ -323,6 +323,34 @@
     }
 
     @Test
+    @Config(minSdk = Build.VERSION_CODES.R)
+    public void testWindowInsets_whenRAndAbove_handlesInsetsCorrectly() {
+        runOnActivity((scenario, activity) -> {
+            IInsetsListener insetsListener = mock(IInsetsListener.class);
+            mRenderServiceDelegate.getCarAppActivity().setInsetsListener(insetsListener);
+            View activityContainer = activity.mActivityContainerView;
+            Insets insets = Insets.of(50, 60, 70, 80);
+            WindowInsets windowInsets = new WindowInsets.Builder().setInsets(
+                    WindowInsets.Type.systemBars(),
+                    insets.toPlatformInsets()).build();
+            activityContainer.onApplyWindowInsets(windowInsets);
+
+            // Verify that system bars insets are handled correctly.
+            verify(insetsListener).onWindowInsetsChanged(eq(insets.toPlatformInsets()),
+                    eq(Insets.NONE.toPlatformInsets()));
+
+            windowInsets = new WindowInsets.Builder().setInsets(
+                    WindowInsets.Type.ime(),
+                    insets.toPlatformInsets()).build();
+            activityContainer.onApplyWindowInsets(windowInsets);
+
+             // Verify that ime insets are handled correctly.
+            verify(insetsListener).onWindowInsetsChanged(eq(insets.toPlatformInsets()),
+                    eq(Insets.NONE.toPlatformInsets()));
+        });
+    }
+
+    @Test
     public void testServiceNotTerminatedWhenConfigurationChanges() {
         runOnActivity((scenario, activity) -> {
             System.out.println("before");
diff --git a/car/app/app-projected/gradle.properties b/car/app/app-projected/gradle.properties
new file mode 100644
index 0000000..a060082
--- /dev/null
+++ b/car/app/app-projected/gradle.properties
@@ -0,0 +1,16 @@
+#
+# Copyright 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+androidx.targetSdkVersion = 33
diff --git a/car/app/app-samples/showcase/automotive/build.gradle b/car/app/app-samples/showcase/automotive/build.gradle
index 41cf3a4..822a062 100644
--- a/car/app/app-samples/showcase/automotive/build.gradle
+++ b/car/app/app-samples/showcase/automotive/build.gradle
@@ -23,7 +23,7 @@
     defaultConfig {
         applicationId "androidx.car.app.sample.showcase"
         minSdkVersion 29
-        targetSdkVersion 31
+        targetSdkVersion 33
         // Increment this to generate signed builds for uploading to Playstore
         // Make sure this is different from the showcase-mobile version
         versionCode 107
diff --git a/car/app/app-samples/showcase/automotive/lint-baseline.xml b/car/app/app-samples/showcase/automotive/lint-baseline.xml
new file mode 100644
index 0000000..a726fae
--- /dev/null
+++ b/car/app/app-samples/showcase/automotive/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.2.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-beta01)" variant="all" version="8.2.0-beta01">
+
+    <issue
+        id="ExpiredTargetSdkVersion"
+        message="Google Play requires that apps target API level 33 or higher."
+        errorLine1="        targetSdkVersion 31"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="build.gradle"/>
+    </issue>
+
+</issues>
diff --git a/car/app/app-samples/showcase/automotive/src/main/AndroidManifestWithSdkVersion.xml b/car/app/app-samples/showcase/automotive/src/main/AndroidManifestWithSdkVersion.xml
index e322348..75945b7 100644
--- a/car/app/app-samples/showcase/automotive/src/main/AndroidManifestWithSdkVersion.xml
+++ b/car/app/app-samples/showcase/automotive/src/main/AndroidManifestWithSdkVersion.xml
@@ -27,7 +27,7 @@
 
   <uses-sdk
       android:minSdkVersion="29"
-      android:targetSdkVersion="31" />
+      android:targetSdkVersion="33" />
 
   <uses-permission android:name="android.permission.INTERNET"/>
   <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-af/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-af/strings.xml
index 5106be1..55ef0b2 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-af/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-af/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Lysoortjie"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Roosteroortjie met lang oortjietitel"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Soekoortjie"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Laai tans oortjie"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Oortjietemplaat laai tans demonstrasie"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Oortjietemplaatdemonstrasie sonder oortjies"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Prente kan nie vir ’n onbekende gasheer gewys word nie"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-am/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-am/strings.xml
index 287b25c..96cfa3f 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-am/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-am/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"የዝርዝር ትር"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"የፍርግርግ ትር ከረጅም ትር ርዕስ ጋር"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"የፍለጋ ትር"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"የመጫኛ ትር"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"የትር ቅንብር ደንብ መጫኛ ቅንጭብ ማሳያ"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"የትር ቅንብር ደንብ ቁጥር ትሮች ቅንጭብ ማሳያ"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"ምስሎች ለማይታወቅ አስተናጋጅ ሊታዩ አይችሉም"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ar/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ar/strings.xml
index a3690a9..d98b591 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ar/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ar/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"علامة تبويب قائمة"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"علامة تبويب شبكة مع عنوان طويل لعلامة التبويب"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"علامة تبويب بحث"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"جارٍ تحميل علامة التبويب"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"عرض توضيحي لنموذج علامة تبويب يجري تحميلها"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"عرض توضيحي لنموذج بلا علامات تبويب"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"يتعذّر عرض الصور لمضيف غير معروف."</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-as/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-as/strings.xml
index 9b69f79..2ce2fd1 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-as/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-as/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"সূচীৰ টেব"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"টেবৰ দীঘলীয়া শিৰোনামৰ সৈতে গ্ৰিডৰ টেব"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"সন্ধানৰ টেব"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"টেব ল’ড কৰি থকা হৈছে"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"টেবৰ টেম্পলে’টৰ ল’ড হৈ থকা ডেম’"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"কোনো টেব নথকা টেবৰ টেম্পলে’টৰ ডেম’"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"অজ্ঞাত হ’ষ্টৰ বাবে প্ৰতিচ্ছবি দেখুৱাব নোৱাৰি"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-az/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-az/strings.xml
index 3483510..71468ba 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-az/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-az/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Siyahı Tabı"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Uzun Tab Başlığı ilə Torlu Tab"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Axtarış Tabı"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Tab yüklənir"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Tab Şablonu - Yükləmə Demosu"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Tab Şablonu - Tabın Olmaması Demosu"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Şəkillər naməlum host üçün göstərilə bilməz"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-b+sr+Latn/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-b+sr+Latn/strings.xml
index f5d2c1f..170974b33 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-b+sr+Latn/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-b+sr+Latn/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Kartica liste"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Kartica sa rešetkom i dugačkim naslovom"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Kartica pretrage"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Učitava se kartica"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demonstracija učitavanja šablona kartice"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demonstracija šablona kartice kada nema kartica"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Slike ne mogu da se prikazuju za nepoznat host"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-be/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-be/strings.xml
index 5c80bd0..2cd921b 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-be/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-be/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Укладка \"Спіс\""</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Укладка з доўгай назвай і змесцівам у выглядзе сеткі"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Укладка \"Пошук\""</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Ідзе загрузка ўкладкі"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Дэмаверсія загрузкі шаблона ўкладкі"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Дэмаверсія шаблона без укладак"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Відарысы нельга адлюстроўваць для невядомага хоста"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-bg/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-bg/strings.xml
index 22f3181..43b15e9 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-bg/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-bg/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Раздел „Списъци“"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Решетка от раздели с дълги заглавия"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Раздел „Търсене“"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Разделът се зарежда"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Шаблонен раздел – демонстрация на зареждането"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Шаблонен раздел – демонстрация без раздели"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Изображенията не могат да бъдат показани за неизвестен хост"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-bn/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-bn/strings.xml
index b42e3d6..5111661 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-bn/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-bn/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"তালিকা ট্যাব"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"ট্যাবের দীর্ঘ শিরোনাম সহ গ্রিড ট্যাব"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"সার্চ ট্যাব"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"\'ট্যাব\' লোড করা হচ্ছে"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"টেম্পলেট লোড করার সম্পর্কিত ডেমো ট্যাব"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"ট্যাব ছাড়াই ট্যাব টেমপ্লেটের ডেমো"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"অজানা হোস্টের জন্য ছবি দেখানো যায় না"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-bs/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-bs/strings.xml
index 9a2e822..e3c97b7 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-bs/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-bs/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Kartica liste"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Kartica mreže s dugim naslovom kartice"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Kartica pretraživanja"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Učitavanje kartice"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demo verzija učitavanja šablona kartice"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demo verzija šablona kartice bez kartice"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Nije moguće prikazati slike za nepoznatog hosta"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ca/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ca/strings.xml
index 26ef199..e7115bc 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ca/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ca/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Pestanya de llista"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Pestanya de quadrícula amb títol llarg"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Pestanya de cerca"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"S\'està carregant la pestanya"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demostració de càrrega d\'una plantilla de pestanya"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demostració d\'una plantilla de pestanya sense pestanyes"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"No es poden mostrar les imatges per a un amfitrió desconegut"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-cs/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-cs/strings.xml
index 4c49f27..e041477 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-cs/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-cs/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Karta se seznamem"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Karta s mřížkou s dlouhým názvem karty"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Karta vyhledávání"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Načítání karty"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Ukázka načítání šablony karty"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Ukázka šablony karty bez karet"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Obrázky nelze zobrazit neznámému hostiteli"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-da/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-da/strings.xml
index 18b72b9..dc1614f 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-da/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-da/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Listefane"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Gitterfane med lang fanetitel"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Søgefane"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Indlæser fanen"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demonstration af indlæsning af faneskabelon"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demonstration af faneskabelon uden faner"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Billeder kan ikke vises for en ukendt host"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-de/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-de/strings.xml
index d84389a..8ebf3ba 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-de/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-de/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Tab „Liste“"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Tab „Raster“ mit langem Tab-Titel"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Tab „Suche“"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Tab wird geladen"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Tab-Vorlage – Demo wird geladen"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Tab-Vorlage – Demo „Keine Tabs“"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Für einen unbekannten Host können keine Bilder angezeigt werden"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-el/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-el/strings.xml
index 3094f92..852f0ea 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-el/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-el/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Καρτέλα λίστας"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Καρτέλα πλέγματος με μεγάλο τίτλο καρτέλας"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Καρτέλα αναζήτησης"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Φόρτωση καρτέλας"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Επίδειξη φόρτωσης προτύπου καρτέλας"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Επίδειξη προτύπου καρτέλας χωρίς καρτέλες"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Δεν είναι δυνατή η εμφάνιση εικόνων για έναν άγνωστο κεντρικό υπολογιστή"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-en-rAU/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-en-rAU/strings.xml
index 5e87d9d0..96cc45b 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-en-rAU/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-en-rAU/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"List tab"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Grid tab with long tab title"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Search tab"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Loading tab"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Tab template loading demo"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Tab template no tabs demo"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Images cannot be displayed for an unknown host"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-en-rCA/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-en-rCA/strings.xml
index 3ac9b76..a645411 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-en-rCA/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-en-rCA/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"List Tab"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Grid Tab with Long Tab Title"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Search Tab"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Loading Tab"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Tab Template Loading Demo"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Tab Template No Tabs Demo"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Images cannot be displayed for an unknown host"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-en-rGB/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-en-rGB/strings.xml
index 5e87d9d0..96cc45b 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-en-rGB/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-en-rGB/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"List tab"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Grid tab with long tab title"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Search tab"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Loading tab"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Tab template loading demo"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Tab template no tabs demo"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Images cannot be displayed for an unknown host"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-en-rIN/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-en-rIN/strings.xml
index 5e87d9d0..96cc45b 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-en-rIN/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-en-rIN/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"List tab"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Grid tab with long tab title"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Search tab"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Loading tab"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Tab template loading demo"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Tab template no tabs demo"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Images cannot be displayed for an unknown host"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-en-rXC/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-en-rXC/strings.xml
index b8771e8..bc10981 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-en-rXC/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-en-rXC/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‏‏‏‏‎‎‎‎‏‎‏‏‏‎‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎‎‏‏‎‏‎‎‎‎‏‏‎‏‏‎List Tab‎‏‎‎‏‎"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‎‎‏‎‏‏‏‎‏‏‎‏‎‏‏‎‏‏‏‎‎‎‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎Grid Tab with Long Tab Title‎‏‎‎‏‎"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‎‏‎‏‎‎‎‎‎‏‎‏‏‏‏‎‎‎‏‏‏‎‎‎‎‏‏‎‎‎‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‎‎‎‏‎‏‎Search Tab‎‏‎‎‏‎"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‏‏‏‏‏‎‎‎‏‏‏‎‏‏‎‏‎‏‏‏‏‎‎‎‎‏‏‎‏‎‎‎‏‏‎‏‏‏‎‏‎‏‎‏‏‏‎‏‏‎‏‏‎‏‎Loading Tab‎‏‎‎‏‎"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‏‏‎‏‎‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‎‎‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎Tab Template Loading Demo‎‏‎‎‏‎"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‎‏‎‎‏‏‎‎‎‏‏‏‎‎‏‏‎‏‎‎‎‎‎‎‏‎‏‎Tab Template No Tabs Demo‎‏‎‎‏‎"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‎‏‎‎‎‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎‎‎‏‏‎‎‏‎‏‎‏‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‎‎‏‏‎‎‎Images cannot be displayed for an unknown host‎‏‎‎‏‎"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-es-rUS/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-es-rUS/strings.xml
index b3d11bb..8ff4d62 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-es-rUS/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-es-rUS/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Pestaña de lista"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Pestaña en cuadrícula con título extenso"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Pestaña de búsqueda"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Cargando pestaña"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demostración de carga de plantilla de pestaña"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demostración de plantilla de pestaña sin pestañas"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"No es posible mostrar imágenes de un host desconocido"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-es/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-es/strings.xml
index a850b68..80cd6ee 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-es/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-es/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Pestaña de lista"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Pestaña de cuadrícula con título largo"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Pestaña de búsqueda"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Cargando pestaña"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demo de carga de plantilla de pestañas"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demo de plantilla de pestañas sin pestañas"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Las imágenes no se pueden mostrar en un host desconocido"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-et/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-et/strings.xml
index 8ebc9a6..c84986a 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-et/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-et/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Loendi vaheleht"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Ruudustiku vaheleht pika nimega"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Otsingu vaheleht"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Vahekaardi laadimine"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Vahelehemalli laadimise demo"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Vahelehemalli vahelehtedeta demo"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Tundmatu hosti puhul ei saa pilte kuvada"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-eu/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-eu/strings.xml
index 8cffc42..1c790f3 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-eu/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-eu/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Zerrendaren fitxa"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Saretaren fitxa izenburu luzearekin"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Bilaketen fitxa"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Fitxa kargatzen"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Fitxen txantiloi bat kargatzearen demo-bertsioa"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Fitxen txantiloien fitxarik gabeko demo-bertsioa"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Ostalari ezezagunei ezin zaizkie bistaratu irudiak"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml
index d606dc5..2b1282c 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"برگه فهرست"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"برگه جدول با عنوان برگه طولانی"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"برگه جستجو"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"درحال بار کردن «برگه»"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"نمونه بارگیری الگوی برگه"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"نمونه عدم وجود برگه الگوی برگه"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"به‌دلیل نامشخص بودن میزبان، تصویر نمایش داده نمی‌شود"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-fi/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-fi/strings.xml
index 02f9fb9..d623ed5 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-fi/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-fi/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Luettelovälilehti"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Ruudukkovälilehti ja pitkä välilehden nimi"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Hakuvälilehti"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Ladataan välilehteä"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Välilehtimallin lataamisen esittely"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Välilehtimallin (ei välilehtiä) esittely"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Kuvia ei voida näyttää tuntemattomalle isännälle"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-fr-rCA/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-fr-rCA/strings.xml
index cec9d82..ae27adf 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-fr-rCA/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-fr-rCA/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Onglet Liste"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Onglet de grille avec un titre d\'onglet long"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Onglet Recherche"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Chargement de l\'onglet en cours…"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Démo du chargement du modèle d\'onglet"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Démo sans onglets du modèle d\'onglet"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Impossible d\'afficher les images pour un hôte inconnu"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-fr/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-fr/strings.xml
index 5b87c06..5b45a10 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-fr/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-fr/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Onglet de liste"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Onglet de grille avec titre d\'onglet long"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Onglet de recherche"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Chargement de l\'onglet"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Démonstration de chargement du modèle d\'onglet"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Démonstrations du modèle d\'onglet sans onglets"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Les images d\'un hôte inconnu ne peuvent pas être affichées"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-gl/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-gl/strings.xml
index 54a5b03..5d559ff 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-gl/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-gl/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Pestana de lista"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Pestana de grade con título de pestana longo"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Pestana de busca"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Cargando pestana"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demostración de carga do modelo de pestanas"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demostración do modelo de pestanas sen pestanas"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"As imaxes non se poden mostrar nun host descoñecido"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-gu/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-gu/strings.xml
index dc883fe..cb90252 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-gu/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-gu/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"સૂચિ ટૅબ"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"ટૅબનું લાંબું શીર્ષક ધરાવતું ગ્રીડ ટૅબ"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"શોધ ટૅબ"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"ટૅબ લોડ થઈ રહ્યું છે"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"ટૅબનો નમૂનો લોડ થવાનો ડેમો"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"ટૅબના નમૂનાનો, ટૅબ વિનાનો ડેમો"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"અજાણ્યા હોસ્ટ માટે છબીઓ બતાવી શકાતી નથી"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-hi/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-hi/strings.xml
index 43bdb56..109e8e6 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-hi/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-hi/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"सूची टैब"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"लंबे टैब टाइटल वाले ग्रिड टैब"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"खोज टैब"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"टैब लोड हो रहा है"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"टैब टेंप्लेट लोड करने का डेमो"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"बिना किसी टैब वाले टैब टेंप्लेट का डेमो"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"अनजान होस्ट के लिए इमेज नहीं दिखाई जा सकतीं"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-hr/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-hr/strings.xml
index 8b4d02e..9cb85ff 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-hr/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-hr/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Kartica popisa"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Kartica rešetke s dugačkim naslovom kartice"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Kartica pretraživanja"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Učitavanje kartice"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Pokazna verzija učitavanja predloška kartice"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Pokazna verzija predloška kartice bez kartica"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Slike se ne mogu prikazati za nepoznatog hosta"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-hu/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-hu/strings.xml
index 9c59d5f..2ee0a9c 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-hu/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-hu/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Listalap"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Rácslap hosszú című lappal"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Keresőlap"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Lap betöltése"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Lapsablon bemutatójának betöltése"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Lapsablon lapok nélküli bemutatója"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"A képek nem jeleníthetők meg ismeretlen gazdagépnek"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-hy/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-hy/strings.xml
index c3c1c32..e114756 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-hy/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-hy/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Ցանկերի ներդիր"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Ցանցերի ներդիր՝ երկար վերնագրով"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Որոնումների ներդիր"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Ներդիրի բեռնում"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Ներդիրների ձևանմուշի բեռնման դեմո"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Առանց ներդիրների ձևանմուշի դեմո"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Պատկերները չեն կարող ցուցադրվել անհայտ խնամորդի համար"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-in/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-in/strings.xml
index a59d4c3..ca12300 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-in/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-in/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Tab Daftar"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Tab Petak dengan Judul Tab Panjang"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Tab Penelusuran"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Memuat Tab"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demo Template Tab Memuat Konten"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demo Template Tab Tidak Ada Tab"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Gambar tidak dapat ditampilkan untuk host yang tidak dikenal"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-is/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-is/strings.xml
index fc6c815..dba3f3a 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-is/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-is/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Listaflipi"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Töfluflipi með löngum flipatitli"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Leitarflipi"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Hleður flipa"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Prufuútgáfa hleðslu flipasniðmáts"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Prufuútgáfa flipasniðmáts með engum flipum"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Ekki er hægt að birta myndir fyrir óþekktan hýsil"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-it/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-it/strings.xml
index e2c1ac3..2122097 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-it/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-it/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Scheda elenco"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Scheda griglia con titolo scheda lungo"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Scheda ricerca"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Caricamento scheda in corso…"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demo caricamento modello scheda"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demo nessuna scheda modello scheda"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Non è possibile mostrare immagini per un host sconosciuto"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-iw/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-iw/strings.xml
index 663a81b..ac062b5 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-iw/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-iw/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"כרטיסייה של רשימה"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"כרטיסיית רשת עם שם כרטיסייה ארוך"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"כרטיסיית חיפוש"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"הכרטיסייה נטענת"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"הדגמה של העלאת תבנית הכרטיסייה"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"הדגמה של תבנית הכרטיסייה כשאין כרטיסיות"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"לא ניתן להציג תמונות למארח לא ידוע"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ja/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ja/strings.xml
index 53722f0..b7c907d 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ja/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ja/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"リストタブ"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"タブのタイトルが長いグリッドタブ"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"検索タブ"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"タブを読み込んでいます"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"タブ テンプレートの読み込みのデモ"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"タブ テンプレートのタブなしのデモ"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"不明なホストでは画像を表示できません"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ka/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ka/strings.xml
index 16c59e0..c174edd 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ka/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ka/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"სიის ჩანართი"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"ბადისებრი ჩანართი გრძელი სათაურით"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"ძიების ჩანართი"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"მიმდინარეობს ჩანართის ჩატვირთვა"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"ჩანართის შაბლონის ჩატვირთვის დემო"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"ჩანართის შაბლონის უჩანართო დემო"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"სურათების უცნობის მასპინძლისთვის ჩვენება შეუძლებელია"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-kk/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-kk/strings.xml
index 214a0a8..e3b064d 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-kk/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-kk/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Тізім қойындысы"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Қойынды атауы ұзын тор қойындысы"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Іздеу қойындысы"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Қойынды жүктеліп жатыр"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Қойынды үлгісінің демо нұсқасын жүктеу"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Қойынды үлгісі – қойындылардың демо нұсқасы жоқ"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Суреттер белгісіз хост үшін көрсетілмейді."</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-km/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-km/strings.xml
index c6d6e72..de18a90 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-km/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-km/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"ផ្ទាំង​​បញ្ជី"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"ផ្ទាំងក្រឡាដែលមានចំណងជើងផ្ទាំងវែង"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"ផ្ទាំងស្វែងរក"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"កំពុងផ្ទុកផ្ទាំង"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"គំរូបង្ហាញផ្ទុកទម្រង់គំរូផ្ទាំង"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"គំរូបង្ហាញគ្មានផ្ទាំងទម្រង់គំរូផ្ទាំង"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"មិនអាចបង្ហាញរូបភាព​សម្រាប់ម៉ាស៊ីនដែល​មិនស្គាល់បានទេ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-kn/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-kn/strings.xml
index 03c20c2..42a4a75 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-kn/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-kn/strings.xml
@@ -162,7 +162,7 @@
     <string name="go_straight" msgid="2301747728609198718">"ನೇರವಾಗಿ ಹೋಗಿ"</string>
     <string name="turn_right" msgid="4710562732720109969">"ಬಲಕ್ಕೆ ತಿರುಗಿ"</string>
     <string name="take_520" msgid="3804796387195842741">"520 ಗೆ ಹೋಗಿ"</string>
-    <string name="gas_station" msgid="1203313937444666161">"ಗ್ಯಾಸ್ ಸ್ಟೇಶನ್"</string>
+    <string name="gas_station" msgid="1203313937444666161">"ಪೆಟ್ರೋಲ್ ಬಂಕ್"</string>
     <string name="short_route" msgid="4831864276538141265">"ಕಡಿಮೆ ದೂರದ ಮಾರ್ಗ"</string>
     <string name="less_busy" msgid="310625272281710983">"ಕಡಿಮೆ ಟ್ರಾಫಿಕ್"</string>
     <string name="hov_friendly" msgid="6956152104754594971">"HOV ಸ್ನೇಹಿ"</string>
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"ಪಟ್ಟಿ ಟ್ಯಾಬ್"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"ಲಾಂಗ್ ಟ್ಯಾಬ್ ಶೀರ್ಷಿಕೆಯೊಂದಿಗೆ ಗ್ರಿಡ್ ಟ್ಯಾಬ್"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"ಹುಡುಕಾಟ ಟ್ಯಾಬ್"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"ಟ್ಯಾಬ್ ಅನ್ನು ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"ಟ್ಯಾಬ್ ಟೆಂಪ್ಲೇಟ್‌‌ ಲೋಡ್ ಆಗುತ್ತಿರುವ ಡೆಮೋ"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"ಟ್ಯಾಬ್ ಟೆಂಪ್ಲೇಟ್‌‌ ಯಾವುದೇ ಟ್ಯಾಬ್‌ಗಳ ಡೆಮೋ ಇಲ್ಲ"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"ಅಪರಿಚಿತ ಹೋಸ್ಟ್‌ಗಾಗಿ ಚಿತ್ರಗಳನ್ನು ಪ್ರದರ್ಶಿಸಲಾಗುವುದಿಲ್ಲ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ko/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ko/strings.xml
index 9bd4030..d3c96b0 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ko/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ko/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"탭 목록"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"긴 탭 제목이 있는 그리드 탭"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"검색 탭"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"탭 로드 중"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"탭 템플릿 로딩 데모"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"탭 없음 탭 템플릿 데모"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"알려지지 않은 호스트의 이미지를 표시할 수 없음"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ky/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ky/strings.xml
index d2cadb3..074c234 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ky/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ky/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Тизме өтмөгү"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Өтмөктүн аталышы узун болгон торчо түрүндөгү өтмөк"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Издөө өтмөгү"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Өтмөк жүктөлүүдө"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Өтмөктүн үлгүсүн жүктөөнүн демо версиясы"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Өтмөктүн үлгүсү, өтмөктөр жок демо версиясы"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Сүрөттөрдү белгисиз башкы түйүн үчүн көрсөтүүгө болбойт"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-lo/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-lo/strings.xml
index c0e7724..6afe958 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-lo/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-lo/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"ແຖບລາຍຊື່"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"ແຖບຕາໜ່າງພ້ອມຊື່ແຖບຍາວ"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"ແຖບຊອກຫາ"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"ກຳລັງໂຫຼດແຖບ"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"ເດໂມແມ່ແບບແຖບການໂຫຼດ"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"ເດໂມແມ່ແບບແຖບທີ່ບໍ່ມີແຖບ"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"ບໍ່ສາມາດສະແດງຮູບສຳລັບໂຮສທີ່ບໍ່ຮູ້ຈັກໄດ້"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-lt/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-lt/strings.xml
index 3a8d187..e919358 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-lt/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-lt/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Sąrašo skirtukas"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Tinklelio skirtukas su ilgu tinklelio pavadinimu"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Paieškos skirtukas"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Įkeliamas skirtukas"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Skirtuko šablono įkėlimo demonstracinė versija"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Skirtuko šablono be skirtukų demonstracinė versija"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Vaizdų negalima pateikti nežinomai prieglobai"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-lv/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-lv/strings.xml
index 03b5db9..4963476 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-lv/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-lv/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Saraksta skata cilne"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Režģa skata cilne ar garu cilnes nosaukumu"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Cilne meklēšanai"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Notiek cilnes ielāde…"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Cilnes veidnes ielādes demonstrācija"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demonstrācija cilnes veidnei, kurā nav ciļņu"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Nevar rādīt attēlus nezināmam saimniekdatoram"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-mk/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-mk/strings.xml
index c37b862..5cf182b 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-mk/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-mk/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Картичка во вид на список"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Картичка во вид на решетка со долг наслов на картичката"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Картичка за пребарување"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Картичката се вчитува"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Демо за вчитување шаблон за картичка"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Демо за немање картички за шаблон за картичка"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Сликите не може да се прикажат за непознат хост"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ml/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ml/strings.xml
index ba91977..76b6ae5 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ml/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ml/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"ലിസ്റ്റ് ടാബ്"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"നീളമേറിയ ടാബ് പേരോട് കൂടിയ ഗ്രിഡ് ടാബ്"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"തിരയൽ ടാബ്"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"ലോഡ് ചെയ്യൽ ടാബ്"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"ടാബ് ടെംപ്ലേറ്റ് ലോഡ് ചെയ്യൽ ഡെമോ"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"ടാബ് ടെംപ്ലേറ്റ് ടാബുകളൊന്നുമില്ല ഡെമോ"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"അജ്ഞാത ഹോസ്റ്റിൽ നിന്നുള്ള ചിത്രങ്ങൾ പ്രദർശിപ്പിക്കാനാകില്ല"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-mn/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-mn/strings.xml
index 88059eb..3ffae53 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-mn/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-mn/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Табын жагсаалт"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Табын урт нэртэй хүснэгтэн таб"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Хайлтын таб"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Табыг ачаалж байна"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Табын загварыг ачаалах демо"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Табын загварын ямар ч табуудын демо байхгүй"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Зургийг тодорхойгүй хостод үзүүлэх боломжгүй"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-mr/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-mr/strings.xml
index 288dfda..b1c63b2 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-mr/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-mr/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"सूची टॅब"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"लांब टॅब शीर्षक असलेला ग्रिड टॅब"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"शोध टॅब"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"टॅब लोड करत आहे"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"टॅब टेंप्लेट लोड होत आहे डेमो"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"टॅब टेंप्लेट टॅब नाही डेमो"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"अज्ञात होस्टला इमेज दाखवल्या जाऊ शकत नाहीत"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ms/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ms/strings.xml
index 8a6ac22..9065afd 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ms/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ms/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Tab Senarai"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Tab Grid dengan Tajuk Tab Panjang"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Tab Carian"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Memuatkan Tab"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demo Memuatkan Templat Tab"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demo Templat Tab Tiada Tab"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Imej tidak boleh dipaparkan untuk hos yang tidak diketahui"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-my/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-my/strings.xml
index 9e38964..2c19281 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-my/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-my/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"စာရင်းတဘ်"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"တဘ်ခေါင်းစဉ်အရှည် ပါဝင်သော ဇယားကွက်တဘ်"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"ရှာဖွေရေးတဘ်"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"တဘ် ဖွင့်နေသည်"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"တဘ်ပုံစံဖွင့်နေသည့် သရုပ်ပြချက်"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"တဘ်ပုံစံတွင် တဘ်မရှိသည့် သရုပ်ပြချက်"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"မသိသောဆာဗာပင်ရင်းအတွက် ပုံများကို ပြ၍မရပါ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-nb/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-nb/strings.xml
index fe2bd0c..53ec16b 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-nb/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-nb/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Listefane"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Rutenettfane med lang fanetittel"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Søkefane"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Laster inn fanen"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Laster inn demo av fanemal"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demo av fanemal uten faner"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Bilder kan ikke vises for en ukjent vert"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ne/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ne/strings.xml
index deed831..79b778d 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ne/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ne/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"लिस्ट ट्याब"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"ट्याबको लामो शीर्षक भएको ग्रिड ट्याब"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"सर्च ट्याब"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"ट्याब लोड गरिँदै छ"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"ट्याब टेम्प्लेट लोड गर्ने डेमो"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"कुनै पनि ट्याब नभएको ट्याब टेम्प्लेटको डेमो"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"अज्ञात होस्टका हकमा फोटोहरू देखाउन मिल्दैन"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-nl/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-nl/strings.xml
index 7317473..e3fbe2c 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-nl/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-nl/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Lijsttabblad"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Rastertabblad met lange tabbladtitel"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Zoektabblad"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Tabblad laden"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demo van laden van tabbladtemplate"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demo van tabbladtemplate zonder tabbladen"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Afbeeldingen kunnen niet worden getoond voor een onbekende host"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-or/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-or/strings.xml
index f01eced..42cf151 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-or/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-or/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"ଲିଷ୍ଟ ଟାବ"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"ଲମ୍ବା ଟାବ ଟାଇଟେଲ ସହ ଗ୍ରୀଡ ଟାବ"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"ସର୍ଚ୍ଚ ଟାବ"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"ଟାବ ଲୋଡ ହେଉଛି"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"ଟାବ ଟେମ୍ପଲେଟର ଲୋଡିଂ ଡେମୋ"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"ଟାବ ଟେମ୍ପଲେଟ କୌଣସି ଟାବ ବିନା ଡେମୋ"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"ଏକ ଅଜଣା ହୋଷ୍ଟ ପାଇଁ ଇମେଜଗୁଡ଼ିକୁ ଡିସପ୍ଲେ କରାଯାଇପାରିବ ନାହିଁ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-pa/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-pa/strings.xml
index 49faa9c..0d162a5 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-pa/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-pa/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"ਸੂਚੀ ਟੈਬ"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"ਲੰਮੇ ਟੈਬ ਸਿਰਲੇਖ ਵਾਲੇ ਗਰਿੱਡ ਟੈਬ"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"ਖੋਜੋ ਟੈਬ"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"ਟੈਬ ਨੂੰ ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"ਟੈਬ ਟੈਮਪਲੇਟ ਲੋਡ ਕਰਨ ਦੀ ਪ੍ਰਕਿਰਿਆ ਦਾ ਡੈਮੋ"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"ਬਿਨਾ ਕਿਸੇ ਟੈਬ ਵਾਲੇ ਟੈਬ ਟੈਮਪਲੇਟ ਦਾ ਡੈਮੋ"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"ਕਿਸੇ ਅਗਿਆਤ ਹੋਸਟ ਲਈ ਚਿੱਤਰ ਨਹੀਂ ਦਿਖਾਏ ਜਾ ਸਕਦੇ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-pl/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-pl/strings.xml
index 7918b4b..9649d73 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-pl/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-pl/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Karta listy"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Wersja demonstracyjna szablonu kart korzystająca z długich kart"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Karta wyszukiwania"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Ładowanie karty"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Ładowanie wersji demonstracyjnej szablonu kart"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Wersja demonstracyjna szablonu kart bez kart"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Nie można wyświetlać grafiki z nieznanego serwera"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-pt-rBR/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-pt-rBR/strings.xml
index d5d524a..146b7c7 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-pt-rBR/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-pt-rBR/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Guia em lista"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Guia em grade com título longo"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Guia de pesquisa"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Carregando a guia"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demonstração de carregamento do modelo de guia"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demonstração do modelo de guia sem guias"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Não é possível exibir imagens para um host desconhecido"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-pt-rPT/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-pt-rPT/strings.xml
index 57ed9c6..3598722 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-pt-rPT/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-pt-rPT/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Separador de lista"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Separador de grelha com título do separador longo"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Separador de pesquisa"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"A carregar o separador"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demonstração do carregamento do modelo de separador"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demonstração do modelo de separador sem separadores"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Não podem ser apresentadas imagens num anfitrião desconhecido"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-pt/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-pt/strings.xml
index d5d524a..146b7c7 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-pt/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-pt/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Guia em lista"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Guia em grade com título longo"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Guia de pesquisa"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Carregando a guia"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demonstração de carregamento do modelo de guia"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demonstração do modelo de guia sem guias"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Não é possível exibir imagens para um host desconhecido"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ro/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ro/strings.xml
index 256f19e..e5f8a3c 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ro/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ro/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Filă cu listă"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Filă cu grilă și titlu lung"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Filă cu căutare"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Se încarcă fila"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demonstrație cu încărcarea șablonului de file"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demonstrație fără file pentru șablon"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Nu se pot afișa imagini pentru o gazdă necunoscută"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ru/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ru/strings.xml
index 8c5e410..0c20c56 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ru/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ru/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Вкладка списка"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Вкладка сетки с длинным названием"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Вкладка поиска"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Загрузка вкладки…"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Демонстрация загрузки шаблона вкладок"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Демонстрация шаблона без вкладок"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Нельзя показывать изображения для неизвестного хоста."</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-si/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-si/strings.xml
index 4a4cf29..146ca3e 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-si/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-si/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"ලැයිස්තු පටිත්ත"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"දිගු පටිති මාතෘකාව සහිත ජාල පටිත්ත"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"සෙවීම් පටිත්ත"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"පටිත්ත පූරණය වේ"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"පටිති අච්චු පූරණය වීමේ ආදර්ශනය"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"පටිති අච්චු පටිති නැති ආදර්ශනය"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"නොදන්නා සංග්‍රාහකයෙක් සඳහා රූප සංදර්ශනය කළ නොහැකිය"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-sk/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-sk/strings.xml
index 560acd0..d494703 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-sk/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-sk/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Karta zoznamu"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Karta mriežky s dlhým názvom karty"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Karta vyhľadávania"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Načítava sa karta"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Ukážka načítavania šablóny karty"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Ukážka šablón karty bez kariet"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Obrázky nie je možné zobraziť pre neznámeho hostiteľa"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-sl/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-sl/strings.xml
index 6cc0da2..1956308 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-sl/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-sl/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Zavihek s seznamom"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Zavihek z mrežo in dolgim naslovom zavihka"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Zavihek za iskanje"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Nalaganje zavihka"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Predstavitvena različica predloge zavihka za stanje nalaganja"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Predstavitvena različica predloge zavihka za stanje brez zavihkov"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Slik ni mogoče prikazati za neznanega gostitelja."</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-sq/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-sq/strings.xml
index b52261b..23a7b4c 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-sq/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-sq/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Skeda e listës"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Skeda e rrjetës me titull të gjatë skede"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Skeda e \"Kërko\""</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Skeda po ngarkohet"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demonstrimi i ngarkimit të shabllonit të skedës"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demonstrimi i shabllonit të skedës pa skeda"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Imazhet nuk mund të shfaqen për një organizator të panjohur"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-sr/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-sr/strings.xml
index eb4dc26..4e692a8 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-sr/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-sr/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Картица листе"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Картица са решетком и дугачким насловом"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Картица претраге"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Учитава се картица"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Демонстрација учитавања шаблона картице"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Демонстрација шаблона картице када нема картица"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Слике не могу да се приказују за непознат хост"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-sv/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-sv/strings.xml
index e708283..8537e2a 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-sv/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-sv/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Listflik"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Elnätsflik med lång fliktitel"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Sökflik"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Läser in flik"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Laddningsflikmallsdemo"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Flikmall utan flikdemo"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Bilder kan inte visas för en okänd värd"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-sw/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-sw/strings.xml
index dc57142..42be6f4 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-sw/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-sw/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Tab ya Orodha"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Tab ya kupangilia kwa Gridi yenye Jina Refu la Tab"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Tab ya Kutafuta"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Inapakia Kichupo"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Kiolezo cha Kupakia Toleo la Kujaribu la Tab"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Kiolezo cha Tab Kisicho na Toleo la Kujaribu la Tab"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Picha haziwezi kuonyeshwa kwa mpangishi asiyejulikana"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ta/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ta/strings.xml
index f17e770..b56be79 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ta/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ta/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"பட்டியல் டேப் (Tab)"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"நீண்ட தலைப்புள்ள கட்டமான டேப் (Tab)"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"தேடல் டேப் (Tab)"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"பிரிவை ஏற்றுகிறது"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"டேப் (Tab) ஏற்றும்போதுள்ள டெம்ப்ளேட் டெமோ"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"டேப் (Tab)  இல்லாதபோதுள்ள டெம்ப்ளேட் டேப்கள் டெமோ"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"அறியப்படாத ஹோஸ்ட்டிற்குப் படங்கள் காட்டப்படாது"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-te/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-te/strings.xml
index c5bf0de..98643fa 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-te/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-te/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"లిస్ట్ ట్యాబ్"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"పొడవైన ట్యాబ్ టైటిల్ గల గ్రిడ్ ట్యాబ్"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"సెర్చ్ ట్యాబ్"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"ట్యాబ్ లోడ్ అవుతోంది"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"ట్యాబ్ టెంప్లేట్‌ను లోడ్ చేస్తున్న డెమో"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"ట్యాబ్‌లు లేని ట్యాబ్ టెంప్లేట్ డెమో"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"తెలియని హోస్ట్ కోసం ఇమేజ్‌లను ప్రదర్శించడం సాధ్యం కాదు"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-th/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-th/strings.xml
index f19bcc2..72d8466 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-th/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-th/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"แท็บรายการ"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"แท็บตารางกริดที่มีชื่อแท็บยาว"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"แท็บค้นหา"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"กำลังโหลดแท็บ"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"เดโมการโหลดเทมเพลตแท็บ"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"เดโมเทมเพลตแท็บที่ไม่มีแท็บ"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"ไม่สามารถแสดงรูปภาพสำหรับโฮสต์ที่ไม่รู้จัก"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-tl/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-tl/strings.xml
index 1b845a3..7db8464 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-tl/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-tl/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Tab ng Listahan"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Grid na Tab na may Mahabang Pamagat ng Tab"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Tab ng Paghahanap"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Nilo-load ang Tab"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demo ng Pag-load ng Template ng Tab"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demo ng Walang Tab na Template ng Tab"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Hindi maipapakita ang mga larawan para sa hindi tukoy na host"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-tr/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-tr/strings.xml
index 6e636da..c56746b 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-tr/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-tr/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Liste Sekmesi"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Uzun Sekme Başlıklı Izgara Sekmesi"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Arama Sekmesi"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Sekme Yükleniyor"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Sekme Şablonu Yükleme Demosu"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Sekme Şablonu Sekme İçermeyen Demo"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Resimler, bilinmeyen ana makine için gösterilemez"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-uk/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-uk/strings.xml
index 6cb6774..3b429dd 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-uk/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-uk/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Вкладка зі списком"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Вкладка із сіткою та довгою назвою"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Вкладка пошуку"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Завантаження вкладки"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Демонстрація шаблона вкладки: завантаження"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Демонстрація шаблона вкладки: немає вкладок"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Зображення не показуються для невідомого хосту"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ur/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ur/strings.xml
index 2fe8e82..d82e510 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ur/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ur/strings.xml
@@ -268,6 +268,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"فہرست ٹیب"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"طویل ٹیب کے عنوان کے ساتھ گرڈ ٹیب"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"تلاش ٹیب"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"ٹیب لوڈ ہو رہا ہے"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"ٹیب کی تمثیل ڈیمو لوڈ ہو رہا ہے"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"ٹیب کی تمثیل کوئی ٹیبز ڈیمو نہیں"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"نامعلوم میزبان کیلئے تصاویر کو ڈسپلے نہیں کیا جا سکتا"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-uz/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-uz/strings.xml
index b604f15..a4f772bd 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-uz/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-uz/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Roʻyxat varagʻi"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Uzun varaq nomi bilan katakli varaq"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Qidiruv varagʻi"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Varaq yuklanmoqda"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Varaq andozasi yuklanish demo versiyasi"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Varaq andozasi varaqsiz demo versiyasi"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Rasmlar notanish xost uchun chiqarilmaydi"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-vi/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-vi/strings.xml
index 334b6e0..69dd820 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-vi/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-vi/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Thẻ dạng danh sách"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Thẻ dạng lưới có tiêu đề thẻ dài"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Thẻ tìm kiếm"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Đang tải thẻ"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Bản demo mẫu thẻ đang tải"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Bản demo mẫu thẻ không có thẻ"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Không thể hiển thị hình ảnh cho một máy chủ không xác định"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-zh-rCN/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-zh-rCN/strings.xml
index 8f96acb..09c29ec 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-zh-rCN/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-zh-rCN/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"列表标签页"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"包含长标签页标题的网格标签页"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"搜索标签页"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"正在加载标签页"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"标签页模板加载演示"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"标签页模板无标签页演示"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"无法为未知主机显示图像"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-zh-rHK/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-zh-rHK/strings.xml
index 5408e2f..a52ba16 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-zh-rHK/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-zh-rHK/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"清單分頁"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"有長分頁標題的格線分頁"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"搜尋分頁"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"正在載入分頁"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"分頁範本載入示範"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"分頁範本無分頁示範"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"無法顯示不明主機的圖片"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-zh-rTW/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-zh-rTW/strings.xml
index 8ace70f..5fe34e8 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-zh-rTW/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-zh-rTW/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"清單分頁"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"有長分頁標題的格線分頁"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"搜尋分頁"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"正在載入分頁"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"分頁範本載入示範"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"分頁範本無分頁示範"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"無法顯示不明主機的圖片"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-zu/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-zu/strings.xml
index 183870c..f37b8a4 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-zu/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-zu/strings.xml
@@ -264,6 +264,7 @@
     <string name="tab_title_list" msgid="5104962518489668123">"Ithebhu Yohlu"</string>
     <string name="tab_title_grid" msgid="5268168907976325154">"Ithebhu yegridi enesihloko sethebhu ende"</string>
     <string name="tab_title_search" msgid="1892925693146631173">"Sesha, ithebhu"</string>
+    <string name="tab_title_loading" msgid="5385807479734490989">"Ilayisha Ithebhu"</string>
     <string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Isifanekiso Sethebhu Silayisha Idemo"</string>
     <string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Isifanekiso Sethebhu Ayikho Idemo Yamathebhu"</string>
     <string name="images_unknown_host_error" msgid="3180661817432720076">"Imifanekiso ayikwazi ukuboniswa mayelana nomsingathi ongaziwa"</string>
diff --git a/car/app/app/gradle.properties b/car/app/app/gradle.properties
new file mode 100644
index 0000000..a060082
--- /dev/null
+++ b/car/app/app/gradle.properties
@@ -0,0 +1,16 @@
+#
+# Copyright 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+androidx.targetSdkVersion = 33
diff --git a/collection/collection/api/current.txt b/collection/collection/api/current.txt
index 5bcab89..ec619f8 100644
--- a/collection/collection/api/current.txt
+++ b/collection/collection/api/current.txt
@@ -94,6 +94,70 @@
     property public final int last;
   }
 
+  public abstract sealed class FloatFloatMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final operator boolean contains(float key);
+    method public final boolean containsKey(float key);
+    method public final boolean containsValue(float value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> block);
+    method public final operator float get(float key);
+    method public final int getCapacity();
+    method public final float getOrDefault(float key, float defaultValue);
+    method public final inline float getOrElse(float key, kotlin.jvm.functions.Function0<java.lang.Float> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+  }
+
+  public final class FloatFloatMapKt {
+    method public static androidx.collection.FloatFloatMap emptyFloatFloatMap();
+    method public static androidx.collection.FloatFloatMap floatFloatMapOf();
+    method public static androidx.collection.FloatFloatMap floatFloatMapOf(kotlin.Pair<java.lang.Float,java.lang.Float>... pairs);
+    method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf();
+    method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf(kotlin.Pair<java.lang.Float,java.lang.Float>... pairs);
+  }
+
+  public abstract sealed class FloatIntMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final operator boolean contains(float key);
+    method public final boolean containsKey(float key);
+    method public final boolean containsValue(int value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final operator int get(float key);
+    method public final int getCapacity();
+    method public final int getOrDefault(float key, int defaultValue);
+    method public final inline int getOrElse(float key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+  }
+
+  public final class FloatIntMapKt {
+    method public static androidx.collection.FloatIntMap emptyFloatIntMap();
+    method public static androidx.collection.FloatIntMap floatIntMapOf();
+    method public static androidx.collection.FloatIntMap floatIntMapOf(kotlin.Pair<java.lang.Float,java.lang.Integer>... pairs);
+    method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf();
+    method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf(kotlin.Pair<java.lang.Float,java.lang.Integer>... pairs);
+  }
+
   public abstract sealed class FloatList {
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
@@ -146,6 +210,70 @@
     method public static inline androidx.collection.MutableFloatList mutableFloatListOf(float... elements);
   }
 
+  public abstract sealed class FloatLongMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final operator boolean contains(float key);
+    method public final boolean containsKey(float key);
+    method public final boolean containsValue(long value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Long,kotlin.Unit> block);
+    method public final operator long get(float key);
+    method public final int getCapacity();
+    method public final long getOrDefault(float key, long defaultValue);
+    method public final inline long getOrElse(float key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+  }
+
+  public final class FloatLongMapKt {
+    method public static androidx.collection.FloatLongMap emptyFloatLongMap();
+    method public static androidx.collection.FloatLongMap floatLongMapOf();
+    method public static androidx.collection.FloatLongMap floatLongMapOf(kotlin.Pair<java.lang.Float,java.lang.Long>... pairs);
+    method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf();
+    method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf(kotlin.Pair<java.lang.Float,java.lang.Long>... pairs);
+  }
+
+  public abstract sealed class FloatObjectMap<V> {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,java.lang.Boolean> predicate);
+    method public final operator boolean contains(float key);
+    method public final boolean containsKey(float key);
+    method public final boolean containsValue(V value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super V,kotlin.Unit> block);
+    method public final operator V? get(float key);
+    method public final int getCapacity();
+    method public final V getOrDefault(float key, V defaultValue);
+    method public final inline V getOrElse(float key, kotlin.jvm.functions.Function0<? extends V> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+  }
+
+  public final class FloatObjectMapKt {
+    method public static <V> androidx.collection.FloatObjectMap<V> emptyFloatObjectMap();
+    method public static <V> androidx.collection.FloatObjectMap<V> floatObjectMapOf();
+    method public static <V> androidx.collection.FloatObjectMap<V> floatObjectMapOf(kotlin.Pair<java.lang.Float,? extends V>... pairs);
+    method public static <V> androidx.collection.MutableFloatObjectMap<V> mutableFloatObjectMapOf();
+    method public static <V> androidx.collection.MutableFloatObjectMap<V> mutableFloatObjectMapOf(kotlin.Pair<java.lang.Float,? extends V>... pairs);
+  }
+
   public abstract sealed class FloatSet {
     method public final inline boolean all(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
     method public final boolean any();
@@ -179,6 +307,70 @@
     method public static androidx.collection.MutableFloatSet mutableFloatSetOf(float... elements);
   }
 
+  public abstract sealed class IntFloatMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final operator boolean contains(int key);
+    method public final boolean containsKey(int key);
+    method public final boolean containsValue(float value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> block);
+    method public final operator float get(int key);
+    method public final int getCapacity();
+    method public final float getOrDefault(int key, float defaultValue);
+    method public final inline float getOrElse(int key, kotlin.jvm.functions.Function0<java.lang.Float> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+  }
+
+  public final class IntFloatMapKt {
+    method public static androidx.collection.IntFloatMap emptyIntFloatMap();
+    method public static androidx.collection.IntFloatMap intFloatMapOf();
+    method public static androidx.collection.IntFloatMap intFloatMapOf(kotlin.Pair<java.lang.Integer,java.lang.Float>... pairs);
+    method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf();
+    method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf(kotlin.Pair<java.lang.Integer,java.lang.Float>... pairs);
+  }
+
+  public abstract sealed class IntIntMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final operator boolean contains(int key);
+    method public final boolean containsKey(int key);
+    method public final boolean containsValue(int value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final operator int get(int key);
+    method public final int getCapacity();
+    method public final int getOrDefault(int key, int defaultValue);
+    method public final inline int getOrElse(int key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+  }
+
+  public final class IntIntMapKt {
+    method public static androidx.collection.IntIntMap emptyIntIntMap();
+    method public static androidx.collection.IntIntMap intIntMapOf();
+    method public static androidx.collection.IntIntMap intIntMapOf(kotlin.Pair<java.lang.Integer,java.lang.Integer>... pairs);
+    method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf();
+    method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf(kotlin.Pair<java.lang.Integer,java.lang.Integer>... pairs);
+  }
+
   public abstract sealed class IntList {
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
@@ -231,6 +423,70 @@
     method public static inline androidx.collection.MutableIntList mutableIntListOf(int... elements);
   }
 
+  public abstract sealed class IntLongMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final operator boolean contains(int key);
+    method public final boolean containsKey(int key);
+    method public final boolean containsValue(long value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Long,kotlin.Unit> block);
+    method public final operator long get(int key);
+    method public final int getCapacity();
+    method public final long getOrDefault(int key, long defaultValue);
+    method public final inline long getOrElse(int key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+  }
+
+  public final class IntLongMapKt {
+    method public static androidx.collection.IntLongMap emptyIntLongMap();
+    method public static androidx.collection.IntLongMap intLongMapOf();
+    method public static androidx.collection.IntLongMap intLongMapOf(kotlin.Pair<java.lang.Integer,java.lang.Long>... pairs);
+    method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf();
+    method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf(kotlin.Pair<java.lang.Integer,java.lang.Long>... pairs);
+  }
+
+  public abstract sealed class IntObjectMap<V> {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,java.lang.Boolean> predicate);
+    method public final operator boolean contains(int key);
+    method public final boolean containsKey(int key);
+    method public final boolean containsValue(V value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super V,kotlin.Unit> block);
+    method public final operator V? get(int key);
+    method public final int getCapacity();
+    method public final V getOrDefault(int key, V defaultValue);
+    method public final inline V getOrElse(int key, kotlin.jvm.functions.Function0<? extends V> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+  }
+
+  public final class IntObjectMapKt {
+    method public static <V> androidx.collection.IntObjectMap<V> emptyIntObjectMap();
+    method public static <V> androidx.collection.IntObjectMap<V> intObjectMapOf();
+    method public static <V> androidx.collection.IntObjectMap<V> intObjectMapOf(kotlin.Pair<java.lang.Integer,? extends V>... pairs);
+    method public static <V> androidx.collection.MutableIntObjectMap<V> mutableIntObjectMapOf();
+    method public static <V> androidx.collection.MutableIntObjectMap<V> mutableIntObjectMapOf(kotlin.Pair<java.lang.Integer,? extends V>... pairs);
+  }
+
   public abstract sealed class IntSet {
     method public final inline boolean all(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
     method public final boolean any();
@@ -264,6 +520,70 @@
     method public static androidx.collection.MutableIntSet mutableIntSetOf(int... elements);
   }
 
+  public abstract sealed class LongFloatMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final operator boolean contains(long key);
+    method public final boolean containsKey(long key);
+    method public final boolean containsValue(float value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Long,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> block);
+    method public final operator float get(long key);
+    method public final int getCapacity();
+    method public final float getOrDefault(long key, float defaultValue);
+    method public final inline float getOrElse(long key, kotlin.jvm.functions.Function0<java.lang.Float> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+  }
+
+  public final class LongFloatMapKt {
+    method public static androidx.collection.LongFloatMap emptyLongFloatMap();
+    method public static androidx.collection.LongFloatMap longFloatMapOf();
+    method public static androidx.collection.LongFloatMap longFloatMapOf(kotlin.Pair<java.lang.Long,java.lang.Float>... pairs);
+    method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf();
+    method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf(kotlin.Pair<java.lang.Long,java.lang.Float>... pairs);
+  }
+
+  public abstract sealed class LongIntMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final operator boolean contains(long key);
+    method public final boolean containsKey(long key);
+    method public final boolean containsValue(int value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Long,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final operator int get(long key);
+    method public final int getCapacity();
+    method public final int getOrDefault(long key, int defaultValue);
+    method public final inline int getOrElse(long key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+  }
+
+  public final class LongIntMapKt {
+    method public static androidx.collection.LongIntMap emptyLongIntMap();
+    method public static androidx.collection.LongIntMap longIntMapOf();
+    method public static androidx.collection.LongIntMap longIntMapOf(kotlin.Pair<java.lang.Long,java.lang.Integer>... pairs);
+    method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf();
+    method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf(kotlin.Pair<java.lang.Long,java.lang.Integer>... pairs);
+  }
+
   public abstract sealed class LongList {
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
@@ -316,6 +636,70 @@
     method public static inline androidx.collection.MutableLongList mutableLongListOf(long... elements);
   }
 
+  public abstract sealed class LongLongMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final operator boolean contains(long key);
+    method public final boolean containsKey(long key);
+    method public final boolean containsValue(long value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Long,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Long,kotlin.Unit> block);
+    method public final operator long get(long key);
+    method public final int getCapacity();
+    method public final long getOrDefault(long key, long defaultValue);
+    method public final inline long getOrElse(long key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+  }
+
+  public final class LongLongMapKt {
+    method public static androidx.collection.LongLongMap emptyLongLongMap();
+    method public static androidx.collection.LongLongMap longLongMapOf();
+    method public static androidx.collection.LongLongMap longLongMapOf(kotlin.Pair<java.lang.Long,java.lang.Long>... pairs);
+    method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf();
+    method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf(kotlin.Pair<java.lang.Long,java.lang.Long>... pairs);
+  }
+
+  public abstract sealed class LongObjectMap<V> {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,java.lang.Boolean> predicate);
+    method public final operator boolean contains(long key);
+    method public final boolean containsKey(long key);
+    method public final boolean containsValue(V value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Long,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super V,kotlin.Unit> block);
+    method public final operator V? get(long key);
+    method public final int getCapacity();
+    method public final V getOrDefault(long key, V defaultValue);
+    method public final inline V getOrElse(long key, kotlin.jvm.functions.Function0<? extends V> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+  }
+
+  public final class LongObjectMapKt {
+    method public static <V> androidx.collection.LongObjectMap<V> emptyLongObjectMap();
+    method public static <V> androidx.collection.LongObjectMap<V> longObjectMapOf();
+    method public static <V> androidx.collection.LongObjectMap<V> longObjectMapOf(kotlin.Pair<java.lang.Long,? extends V>... pairs);
+    method public static <V> androidx.collection.MutableLongObjectMap<V> mutableLongObjectMapOf();
+    method public static <V> androidx.collection.MutableLongObjectMap<V> mutableLongObjectMapOf(kotlin.Pair<java.lang.Long,? extends V>... pairs);
+  }
+
   public abstract sealed class LongSet {
     method public final inline boolean all(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
     method public final boolean any();
@@ -416,6 +800,48 @@
     method public static inline <K, V> androidx.collection.LruCache<K,V> lruCache(int maxSize, optional kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf, optional kotlin.jvm.functions.Function1<? super K,? extends V> create, optional kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved);
   }
 
+  public final class MutableFloatFloatMap extends androidx.collection.FloatFloatMap {
+    ctor public MutableFloatFloatMap(optional int initialCapacity);
+    method public void clear();
+    method public inline float getOrPut(float key, kotlin.jvm.functions.Function0<java.lang.Float> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.FloatList keys);
+    method public inline operator void minusAssign(androidx.collection.FloatSet keys);
+    method public inline operator void minusAssign(float key);
+    method public inline operator void minusAssign(float[] keys);
+    method public inline operator void plusAssign(androidx.collection.FloatFloatMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Float,java.lang.Float> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Float,java.lang.Float>![] pairs);
+    method public void put(float key, float value);
+    method public void putAll(androidx.collection.FloatFloatMap from);
+    method public void putAll(kotlin.Pair<java.lang.Float,java.lang.Float>![] pairs);
+    method public void remove(float key);
+    method public boolean remove(float key, float value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public operator void set(float key, float value);
+    method public int trim();
+  }
+
+  public final class MutableFloatIntMap extends androidx.collection.FloatIntMap {
+    ctor public MutableFloatIntMap(optional int initialCapacity);
+    method public void clear();
+    method public inline int getOrPut(float key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.FloatList keys);
+    method public inline operator void minusAssign(androidx.collection.FloatSet keys);
+    method public inline operator void minusAssign(float key);
+    method public inline operator void minusAssign(float[] keys);
+    method public inline operator void plusAssign(androidx.collection.FloatIntMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Float,java.lang.Integer> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Float,java.lang.Integer>![] pairs);
+    method public void put(float key, int value);
+    method public void putAll(androidx.collection.FloatIntMap from);
+    method public void putAll(kotlin.Pair<java.lang.Float,java.lang.Integer>![] pairs);
+    method public void remove(float key);
+    method public boolean remove(float key, int value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public operator void set(float key, int value);
+    method public int trim();
+  }
+
   public final class MutableFloatList extends androidx.collection.FloatList {
     ctor public MutableFloatList(optional int initialCapacity);
     method public boolean add(float element);
@@ -447,6 +873,48 @@
     property public final inline int capacity;
   }
 
+  public final class MutableFloatLongMap extends androidx.collection.FloatLongMap {
+    ctor public MutableFloatLongMap(optional int initialCapacity);
+    method public void clear();
+    method public inline long getOrPut(float key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.FloatList keys);
+    method public inline operator void minusAssign(androidx.collection.FloatSet keys);
+    method public inline operator void minusAssign(float key);
+    method public inline operator void minusAssign(float[] keys);
+    method public inline operator void plusAssign(androidx.collection.FloatLongMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Float,java.lang.Long> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Float,java.lang.Long>![] pairs);
+    method public void put(float key, long value);
+    method public void putAll(androidx.collection.FloatLongMap from);
+    method public void putAll(kotlin.Pair<java.lang.Float,java.lang.Long>![] pairs);
+    method public void remove(float key);
+    method public boolean remove(float key, long value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public operator void set(float key, long value);
+    method public int trim();
+  }
+
+  public final class MutableFloatObjectMap<V> extends androidx.collection.FloatObjectMap<V> {
+    ctor public MutableFloatObjectMap(optional int initialCapacity);
+    method public void clear();
+    method public inline V getOrPut(float key, kotlin.jvm.functions.Function0<? extends V> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.FloatList keys);
+    method public inline operator void minusAssign(androidx.collection.FloatSet keys);
+    method public inline operator void minusAssign(float key);
+    method public inline operator void minusAssign(float[] keys);
+    method public inline operator void plusAssign(androidx.collection.FloatObjectMap<V> from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Float,? extends V> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Float,? extends V>![] pairs);
+    method public V? put(float key, V value);
+    method public void putAll(androidx.collection.FloatObjectMap<V> from);
+    method public void putAll(kotlin.Pair<java.lang.Float,? extends V>![] pairs);
+    method public V? remove(float key);
+    method public boolean remove(float key, V value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,java.lang.Boolean> predicate);
+    method public operator void set(float key, V value);
+    method public int trim();
+  }
+
   public final class MutableFloatSet extends androidx.collection.FloatSet {
     ctor public MutableFloatSet(optional int initialCapacity);
     method public boolean add(float element);
@@ -465,6 +933,48 @@
     method @IntRange(from=0L) public int trim();
   }
 
+  public final class MutableIntFloatMap extends androidx.collection.IntFloatMap {
+    ctor public MutableIntFloatMap(optional int initialCapacity);
+    method public void clear();
+    method public inline float getOrPut(int key, kotlin.jvm.functions.Function0<java.lang.Float> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.IntList keys);
+    method public inline operator void minusAssign(androidx.collection.IntSet keys);
+    method public inline operator void minusAssign(int key);
+    method public inline operator void minusAssign(int[] keys);
+    method public inline operator void plusAssign(androidx.collection.IntFloatMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Integer,java.lang.Float> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Integer,java.lang.Float>![] pairs);
+    method public void put(int key, float value);
+    method public void putAll(androidx.collection.IntFloatMap from);
+    method public void putAll(kotlin.Pair<java.lang.Integer,java.lang.Float>![] pairs);
+    method public void remove(int key);
+    method public boolean remove(int key, float value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public operator void set(int key, float value);
+    method public int trim();
+  }
+
+  public final class MutableIntIntMap extends androidx.collection.IntIntMap {
+    ctor public MutableIntIntMap(optional int initialCapacity);
+    method public void clear();
+    method public inline int getOrPut(int key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.IntList keys);
+    method public inline operator void minusAssign(androidx.collection.IntSet keys);
+    method public inline operator void minusAssign(int key);
+    method public inline operator void minusAssign(int[] keys);
+    method public inline operator void plusAssign(androidx.collection.IntIntMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Integer,java.lang.Integer> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Integer,java.lang.Integer>![] pairs);
+    method public void put(int key, int value);
+    method public void putAll(androidx.collection.IntIntMap from);
+    method public void putAll(kotlin.Pair<java.lang.Integer,java.lang.Integer>![] pairs);
+    method public void remove(int key);
+    method public boolean remove(int key, int value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public operator void set(int key, int value);
+    method public int trim();
+  }
+
   public final class MutableIntList extends androidx.collection.IntList {
     ctor public MutableIntList(optional int initialCapacity);
     method public boolean add(int element);
@@ -496,6 +1006,48 @@
     property public final inline int capacity;
   }
 
+  public final class MutableIntLongMap extends androidx.collection.IntLongMap {
+    ctor public MutableIntLongMap(optional int initialCapacity);
+    method public void clear();
+    method public inline long getOrPut(int key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.IntList keys);
+    method public inline operator void minusAssign(androidx.collection.IntSet keys);
+    method public inline operator void minusAssign(int key);
+    method public inline operator void minusAssign(int[] keys);
+    method public inline operator void plusAssign(androidx.collection.IntLongMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Integer,java.lang.Long> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Integer,java.lang.Long>![] pairs);
+    method public void put(int key, long value);
+    method public void putAll(androidx.collection.IntLongMap from);
+    method public void putAll(kotlin.Pair<java.lang.Integer,java.lang.Long>![] pairs);
+    method public void remove(int key);
+    method public boolean remove(int key, long value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public operator void set(int key, long value);
+    method public int trim();
+  }
+
+  public final class MutableIntObjectMap<V> extends androidx.collection.IntObjectMap<V> {
+    ctor public MutableIntObjectMap(optional int initialCapacity);
+    method public void clear();
+    method public inline V getOrPut(int key, kotlin.jvm.functions.Function0<? extends V> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.IntList keys);
+    method public inline operator void minusAssign(androidx.collection.IntSet keys);
+    method public inline operator void minusAssign(int key);
+    method public inline operator void minusAssign(int[] keys);
+    method public inline operator void plusAssign(androidx.collection.IntObjectMap<V> from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Integer,? extends V> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Integer,? extends V>![] pairs);
+    method public V? put(int key, V value);
+    method public void putAll(androidx.collection.IntObjectMap<V> from);
+    method public void putAll(kotlin.Pair<java.lang.Integer,? extends V>![] pairs);
+    method public V? remove(int key);
+    method public boolean remove(int key, V value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,java.lang.Boolean> predicate);
+    method public operator void set(int key, V value);
+    method public int trim();
+  }
+
   public final class MutableIntSet extends androidx.collection.IntSet {
     ctor public MutableIntSet(optional int initialCapacity);
     method public boolean add(int element);
@@ -514,6 +1066,48 @@
     method @IntRange(from=0L) public int trim();
   }
 
+  public final class MutableLongFloatMap extends androidx.collection.LongFloatMap {
+    ctor public MutableLongFloatMap(optional int initialCapacity);
+    method public void clear();
+    method public inline float getOrPut(long key, kotlin.jvm.functions.Function0<java.lang.Float> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.LongList keys);
+    method public inline operator void minusAssign(androidx.collection.LongSet keys);
+    method public inline operator void minusAssign(long key);
+    method public inline operator void minusAssign(long[] keys);
+    method public inline operator void plusAssign(androidx.collection.LongFloatMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Long,java.lang.Float> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Long,java.lang.Float>![] pairs);
+    method public void put(long key, float value);
+    method public void putAll(androidx.collection.LongFloatMap from);
+    method public void putAll(kotlin.Pair<java.lang.Long,java.lang.Float>![] pairs);
+    method public void remove(long key);
+    method public boolean remove(long key, float value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public operator void set(long key, float value);
+    method public int trim();
+  }
+
+  public final class MutableLongIntMap extends androidx.collection.LongIntMap {
+    ctor public MutableLongIntMap(optional int initialCapacity);
+    method public void clear();
+    method public inline int getOrPut(long key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.LongList keys);
+    method public inline operator void minusAssign(androidx.collection.LongSet keys);
+    method public inline operator void minusAssign(long key);
+    method public inline operator void minusAssign(long[] keys);
+    method public inline operator void plusAssign(androidx.collection.LongIntMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Long,java.lang.Integer> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Long,java.lang.Integer>![] pairs);
+    method public void put(long key, int value);
+    method public void putAll(androidx.collection.LongIntMap from);
+    method public void putAll(kotlin.Pair<java.lang.Long,java.lang.Integer>![] pairs);
+    method public void remove(long key);
+    method public boolean remove(long key, int value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public operator void set(long key, int value);
+    method public int trim();
+  }
+
   public final class MutableLongList extends androidx.collection.LongList {
     ctor public MutableLongList(optional int initialCapacity);
     method public void add(@IntRange(from=0L) int index, long element);
@@ -545,6 +1139,48 @@
     property public final inline int capacity;
   }
 
+  public final class MutableLongLongMap extends androidx.collection.LongLongMap {
+    ctor public MutableLongLongMap(optional int initialCapacity);
+    method public void clear();
+    method public inline long getOrPut(long key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.LongList keys);
+    method public inline operator void minusAssign(androidx.collection.LongSet keys);
+    method public inline operator void minusAssign(long key);
+    method public inline operator void minusAssign(long[] keys);
+    method public inline operator void plusAssign(androidx.collection.LongLongMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Long,java.lang.Long> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Long,java.lang.Long>![] pairs);
+    method public void put(long key, long value);
+    method public void putAll(androidx.collection.LongLongMap from);
+    method public void putAll(kotlin.Pair<java.lang.Long,java.lang.Long>![] pairs);
+    method public void remove(long key);
+    method public boolean remove(long key, long value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public operator void set(long key, long value);
+    method public int trim();
+  }
+
+  public final class MutableLongObjectMap<V> extends androidx.collection.LongObjectMap<V> {
+    ctor public MutableLongObjectMap(optional int initialCapacity);
+    method public void clear();
+    method public inline V getOrPut(long key, kotlin.jvm.functions.Function0<? extends V> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.LongList keys);
+    method public inline operator void minusAssign(androidx.collection.LongSet keys);
+    method public inline operator void minusAssign(long key);
+    method public inline operator void minusAssign(long[] keys);
+    method public inline operator void plusAssign(androidx.collection.LongObjectMap<V> from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Long,? extends V> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Long,? extends V>![] pairs);
+    method public V? put(long key, V value);
+    method public void putAll(androidx.collection.LongObjectMap<V> from);
+    method public void putAll(kotlin.Pair<java.lang.Long,? extends V>![] pairs);
+    method public V? remove(long key);
+    method public boolean remove(long key, V value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,java.lang.Boolean> predicate);
+    method public operator void set(long key, V value);
+    method public int trim();
+  }
+
   public final class MutableLongSet extends androidx.collection.LongSet {
     ctor public MutableLongSet(optional int initialCapacity);
     method public boolean add(long element);
@@ -563,6 +1199,124 @@
     method @IntRange(from=0L) public int trim();
   }
 
+  public final class MutableObjectFloatMap<K> extends androidx.collection.ObjectFloatMap<K> {
+    ctor public MutableObjectFloatMap(optional int initialCapacity);
+    method public void clear();
+    method public inline float getOrPut(K key, kotlin.jvm.functions.Function0<java.lang.Float> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.ScatterSet<K> keys);
+    method public inline operator void minusAssign(Iterable<? extends K> keys);
+    method public inline operator void minusAssign(K key);
+    method public inline operator void minusAssign(K![] keys);
+    method public inline operator void minusAssign(kotlin.sequences.Sequence<? extends K> keys);
+    method public inline operator void plusAssign(androidx.collection.ObjectFloatMap<K> from);
+    method public inline operator void plusAssign(kotlin.Pair<? extends K,java.lang.Float> pair);
+    method public inline operator void plusAssign(kotlin.Pair<? extends K,java.lang.Float>![] pairs);
+    method public void put(K key, float value);
+    method public void putAll(androidx.collection.ObjectFloatMap<K> from);
+    method public void putAll(kotlin.Pair<? extends K,java.lang.Float>![] pairs);
+    method public void remove(K key);
+    method public boolean remove(K key, float value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public operator void set(K key, float value);
+    method public int trim();
+  }
+
+  public final class MutableObjectIntMap<K> extends androidx.collection.ObjectIntMap<K> {
+    ctor public MutableObjectIntMap(optional int initialCapacity);
+    method public void clear();
+    method public inline int getOrPut(K key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.ScatterSet<K> keys);
+    method public inline operator void minusAssign(Iterable<? extends K> keys);
+    method public inline operator void minusAssign(K key);
+    method public inline operator void minusAssign(K![] keys);
+    method public inline operator void minusAssign(kotlin.sequences.Sequence<? extends K> keys);
+    method public inline operator void plusAssign(androidx.collection.ObjectIntMap<K> from);
+    method public inline operator void plusAssign(kotlin.Pair<? extends K,java.lang.Integer> pair);
+    method public inline operator void plusAssign(kotlin.Pair<? extends K,java.lang.Integer>![] pairs);
+    method public void put(K key, int value);
+    method public void putAll(androidx.collection.ObjectIntMap<K> from);
+    method public void putAll(kotlin.Pair<? extends K,java.lang.Integer>![] pairs);
+    method public void remove(K key);
+    method public boolean remove(K key, int value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public operator void set(K key, int value);
+    method public int trim();
+  }
+
+  public final class MutableObjectList<E> extends androidx.collection.ObjectList<E> {
+    ctor public MutableObjectList(optional int initialCapacity);
+    method public boolean add(E element);
+    method public void add(@IntRange(from=0L) int index, E element);
+    method public boolean addAll(androidx.collection.ObjectList<E> elements);
+    method public boolean addAll(androidx.collection.ScatterSet<E> elements);
+    method public boolean addAll(E![] elements);
+    method public boolean addAll(@IntRange(from=0L) int index, androidx.collection.ObjectList<E> elements);
+    method public boolean addAll(@IntRange(from=0L) int index, E![] elements);
+    method public boolean addAll(@IntRange(from=0L) int index, java.util.Collection<? extends E> elements);
+    method public boolean addAll(Iterable<? extends E> elements);
+    method public boolean addAll(java.util.List<? extends E> elements);
+    method public boolean addAll(kotlin.sequences.Sequence<? extends E> elements);
+    method public java.util.List<E> asList();
+    method public java.util.List<E> asMutableList();
+    method public void clear();
+    method public void ensureCapacity(int capacity);
+    method public inline int getCapacity();
+    method public operator void minusAssign(androidx.collection.ObjectList<E> elements);
+    method public operator void minusAssign(androidx.collection.ScatterSet<E> elements);
+    method public inline operator void minusAssign(E element);
+    method public operator void minusAssign(E![] elements);
+    method public operator void minusAssign(Iterable<? extends E> elements);
+    method public operator void minusAssign(java.util.List<? extends E> elements);
+    method public operator void minusAssign(kotlin.sequences.Sequence<? extends E> elements);
+    method public operator void plusAssign(androidx.collection.ObjectList<E> elements);
+    method public operator void plusAssign(androidx.collection.ScatterSet<E> elements);
+    method public inline operator void plusAssign(E element);
+    method public operator void plusAssign(E![] elements);
+    method public operator void plusAssign(Iterable<? extends E> elements);
+    method public operator void plusAssign(java.util.List<? extends E> elements);
+    method public operator void plusAssign(kotlin.sequences.Sequence<? extends E> elements);
+    method public boolean remove(E element);
+    method public boolean removeAll(androidx.collection.ObjectList<E> elements);
+    method public boolean removeAll(androidx.collection.ScatterSet<E> elements);
+    method public boolean removeAll(E![] elements);
+    method public boolean removeAll(Iterable<? extends E> elements);
+    method public boolean removeAll(java.util.List<? extends E> elements);
+    method public boolean removeAll(kotlin.sequences.Sequence<? extends E> elements);
+    method public E removeAt(@IntRange(from=0L) int index);
+    method public inline void removeIf(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public void removeRange(@IntRange(from=0L) int start, @IntRange(from=0L) int end);
+    method public boolean retainAll(androidx.collection.ObjectList<E> elements);
+    method public boolean retainAll(E![] elements);
+    method public boolean retainAll(Iterable<? extends E> elements);
+    method public boolean retainAll(java.util.Collection<? extends E> elements);
+    method public boolean retainAll(kotlin.sequences.Sequence<? extends E> elements);
+    method public operator E set(@IntRange(from=0L) int index, E element);
+    method public void trim(optional int minCapacity);
+    property public final inline int capacity;
+  }
+
+  public final class MutableObjectLongMap<K> extends androidx.collection.ObjectLongMap<K> {
+    ctor public MutableObjectLongMap(optional int initialCapacity);
+    method public void clear();
+    method public inline long getOrPut(K key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.ScatterSet<K> keys);
+    method public inline operator void minusAssign(Iterable<? extends K> keys);
+    method public inline operator void minusAssign(K key);
+    method public inline operator void minusAssign(K![] keys);
+    method public inline operator void minusAssign(kotlin.sequences.Sequence<? extends K> keys);
+    method public inline operator void plusAssign(androidx.collection.ObjectLongMap<K> from);
+    method public inline operator void plusAssign(kotlin.Pair<? extends K,java.lang.Long> pair);
+    method public inline operator void plusAssign(kotlin.Pair<? extends K,java.lang.Long>![] pairs);
+    method public void put(K key, long value);
+    method public void putAll(androidx.collection.ObjectLongMap<K> from);
+    method public void putAll(kotlin.Pair<? extends K,java.lang.Long>![] pairs);
+    method public void remove(K key);
+    method public boolean remove(K key, long value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public operator void set(K key, long value);
+    method public int trim();
+  }
+
   public final class MutableScatterMap<K, V> extends androidx.collection.ScatterMap<K,V> {
     ctor public MutableScatterMap(optional int initialCapacity);
     method public java.util.Map<K,V> asMutableMap();
@@ -619,6 +1373,162 @@
     method @IntRange(from=0L) public int trim();
   }
 
+  public abstract sealed class ObjectFloatMap<K> {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final operator boolean contains(K key);
+    method public final boolean containsKey(K key);
+    method public final boolean containsValue(float value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super K,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> block);
+    method public final operator float get(K key);
+    method public final int getCapacity();
+    method public final float getOrDefault(K key, float defaultValue);
+    method public final inline float getOrElse(K key, kotlin.jvm.functions.Function0<java.lang.Float> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+  }
+
+  public final class ObjectFloatMapKt {
+    method public static <K> androidx.collection.ObjectFloatMap<K> emptyObjectFloatMap();
+    method public static <K> androidx.collection.MutableObjectFloatMap<K> mutableObjectFloatMapOf();
+    method public static <K> androidx.collection.MutableObjectFloatMap<K> mutableObjectFloatMapOf(kotlin.Pair<? extends K,java.lang.Float>... pairs);
+    method public static <K> androidx.collection.ObjectFloatMap<K> objectFloatMap();
+    method public static <K> androidx.collection.ObjectFloatMap<K> objectFloatMapOf(kotlin.Pair<? extends K,java.lang.Float>... pairs);
+  }
+
+  public abstract sealed class ObjectIntMap<K> {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final operator boolean contains(K key);
+    method public final boolean containsKey(K key);
+    method public final boolean containsValue(int value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super K,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final operator int get(K key);
+    method public final int getCapacity();
+    method public final int getOrDefault(K key, int defaultValue);
+    method public final inline int getOrElse(K key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+  }
+
+  public final class ObjectIntMapKt {
+    method public static <K> androidx.collection.ObjectIntMap<K> emptyObjectIntMap();
+    method public static <K> androidx.collection.MutableObjectIntMap<K> mutableObjectIntMapOf();
+    method public static <K> androidx.collection.MutableObjectIntMap<K> mutableObjectIntMapOf(kotlin.Pair<? extends K,java.lang.Integer>... pairs);
+    method public static <K> androidx.collection.ObjectIntMap<K> objectIntMap();
+    method public static <K> androidx.collection.ObjectIntMap<K> objectIntMapOf(kotlin.Pair<? extends K,java.lang.Integer>... pairs);
+  }
+
+  public abstract sealed class ObjectList<E> {
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public abstract java.util.List<E> asList();
+    method public final operator boolean contains(E element);
+    method public final boolean containsAll(androidx.collection.ObjectList<E> elements);
+    method public final boolean containsAll(E![] elements);
+    method public final boolean containsAll(Iterable<? extends E> elements);
+    method public final boolean containsAll(java.util.List<? extends E> elements);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final E elementAt(@IntRange(from=0L) int index);
+    method public final inline E elementAtOrElse(@IntRange(from=0L) int index, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends E> defaultValue);
+    method public final E first();
+    method public final inline E first(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final inline E? firstOrNull();
+    method public final inline E? firstOrNull(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final inline <R> R fold(R initial, kotlin.jvm.functions.Function2<? super R,? super E,? extends R> operation);
+    method public final inline <R> R foldIndexed(R initial, kotlin.jvm.functions.Function3<? super java.lang.Integer,? super R,? super E,? extends R> operation);
+    method public final inline <R> R foldRight(R initial, kotlin.jvm.functions.Function2<? super E,? super R,? extends R> operation);
+    method public final inline <R> R foldRightIndexed(R initial, kotlin.jvm.functions.Function3<? super java.lang.Integer,? super E,? super R,? extends R> operation);
+    method public final inline void forEach(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+    method public final inline void forEachIndexed(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super E,kotlin.Unit> block);
+    method public final inline void forEachReversed(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+    method public final inline void forEachReversedIndexed(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super E,kotlin.Unit> block);
+    method public final operator E get(@IntRange(from=0L) int index);
+    method public final inline kotlin.ranges.IntRange getIndices();
+    method @IntRange(from=-1L) public final inline int getLastIndex();
+    method @IntRange(from=0L) public final int getSize();
+    method public final int indexOf(E element);
+    method public final inline int indexOfFirst(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final E last();
+    method public final inline E last(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final int lastIndexOf(E element);
+    method public final inline E? lastOrNull();
+    method public final inline E? lastOrNull(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final boolean none();
+    method public final inline boolean reversedAny(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    property public final inline kotlin.ranges.IntRange indices;
+    property @IntRange(from=-1L) public final inline int lastIndex;
+    property @IntRange(from=0L) public final int size;
+  }
+
+  public final class ObjectListKt {
+    method public static <E> androidx.collection.ObjectList<E> emptyObjectList();
+    method public static inline <E> androidx.collection.MutableObjectList<E> mutableObjectListOf();
+    method public static <E> androidx.collection.MutableObjectList<E> mutableObjectListOf(E element1);
+    method public static <E> androidx.collection.MutableObjectList<E> mutableObjectListOf(E element1, E element2);
+    method public static <E> androidx.collection.MutableObjectList<E> mutableObjectListOf(E element1, E element2, E element3);
+    method public static inline <E> androidx.collection.MutableObjectList<E> mutableObjectListOf(E?... elements);
+    method public static <E> androidx.collection.ObjectList<E> objectListOf();
+    method public static <E> androidx.collection.ObjectList<E> objectListOf(E element1);
+    method public static <E> androidx.collection.ObjectList<E> objectListOf(E element1, E element2);
+    method public static <E> androidx.collection.ObjectList<E> objectListOf(E element1, E element2, E element3);
+    method public static <E> androidx.collection.ObjectList<E> objectListOf(E?... elements);
+  }
+
+  public abstract sealed class ObjectLongMap<K> {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final operator boolean contains(K key);
+    method public final boolean containsKey(K key);
+    method public final boolean containsValue(long value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super K,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Long,kotlin.Unit> block);
+    method public final operator long get(K key);
+    method public final int getCapacity();
+    method public final long getOrDefault(K key, long defaultValue);
+    method public final inline long getOrElse(K key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+  }
+
+  public final class ObjectLongMapKt {
+    method public static <K> androidx.collection.ObjectLongMap<K> emptyObjectLongMap();
+    method public static <K> androidx.collection.MutableObjectLongMap<K> mutableObjectLongMapOf();
+    method public static <K> androidx.collection.MutableObjectLongMap<K> mutableObjectLongMapOf(kotlin.Pair<? extends K,java.lang.Long>... pairs);
+    method public static <K> androidx.collection.ObjectLongMap<K> objectLongMap();
+    method public static <K> androidx.collection.ObjectLongMap<K> objectLongMapOf(kotlin.Pair<? extends K,java.lang.Long>... pairs);
+  }
+
   @kotlin.jvm.JvmInline public final value class PairFloatFloat {
     ctor public PairFloatFloat(float first, float second);
     method public inline operator float component1();
diff --git a/collection/collection/api/restricted_current.txt b/collection/collection/api/restricted_current.txt
index a46df5c..098c0eb 100644
--- a/collection/collection/api/restricted_current.txt
+++ b/collection/collection/api/restricted_current.txt
@@ -94,6 +94,80 @@
     property public final int last;
   }
 
+  public abstract sealed class FloatFloatMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final operator boolean contains(float key);
+    method public final boolean containsKey(float key);
+    method public final boolean containsValue(float value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean> predicate);
+    method @kotlin.PublishedApi internal final int findKeyIndex(float key);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,kotlin.Unit> block);
+    method @kotlin.PublishedApi internal final inline void forEachIndexed(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> block);
+    method public final operator float get(float key);
+    method public final int getCapacity();
+    method public final float getOrDefault(float key, float defaultValue);
+    method public final inline float getOrElse(float key, kotlin.jvm.functions.Function0<java.lang.Float> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+    field @kotlin.PublishedApi internal float[] keys;
+    field @kotlin.PublishedApi internal long[] metadata;
+    field @kotlin.PublishedApi internal float[] values;
+  }
+
+  public final class FloatFloatMapKt {
+    method public static androidx.collection.FloatFloatMap emptyFloatFloatMap();
+    method public static androidx.collection.FloatFloatMap floatFloatMapOf();
+    method public static androidx.collection.FloatFloatMap floatFloatMapOf(kotlin.Pair<java.lang.Float,java.lang.Float>... pairs);
+    method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf();
+    method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf(kotlin.Pair<java.lang.Float,java.lang.Float>... pairs);
+  }
+
+  public abstract sealed class FloatIntMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final operator boolean contains(float key);
+    method public final boolean containsKey(float key);
+    method public final boolean containsValue(int value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method @kotlin.PublishedApi internal final int findKeyIndex(float key);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,kotlin.Unit> block);
+    method @kotlin.PublishedApi internal final inline void forEachIndexed(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final operator int get(float key);
+    method public final int getCapacity();
+    method public final int getOrDefault(float key, int defaultValue);
+    method public final inline int getOrElse(float key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+    field @kotlin.PublishedApi internal float[] keys;
+    field @kotlin.PublishedApi internal long[] metadata;
+    field @kotlin.PublishedApi internal int[] values;
+  }
+
+  public final class FloatIntMapKt {
+    method public static androidx.collection.FloatIntMap emptyFloatIntMap();
+    method public static androidx.collection.FloatIntMap floatIntMapOf();
+    method public static androidx.collection.FloatIntMap floatIntMapOf(kotlin.Pair<java.lang.Float,java.lang.Integer>... pairs);
+    method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf();
+    method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf(kotlin.Pair<java.lang.Float,java.lang.Integer>... pairs);
+  }
+
   public abstract sealed class FloatList {
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
@@ -148,6 +222,79 @@
     method public static inline androidx.collection.MutableFloatList mutableFloatListOf(float... elements);
   }
 
+  public abstract sealed class FloatLongMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final operator boolean contains(float key);
+    method public final boolean containsKey(float key);
+    method public final boolean containsValue(long value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,java.lang.Boolean> predicate);
+    method @kotlin.PublishedApi internal final int findKeyIndex(float key);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,kotlin.Unit> block);
+    method @kotlin.PublishedApi internal final inline void forEachIndexed(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Long,kotlin.Unit> block);
+    method public final operator long get(float key);
+    method public final int getCapacity();
+    method public final long getOrDefault(float key, long defaultValue);
+    method public final inline long getOrElse(float key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+    field @kotlin.PublishedApi internal float[] keys;
+    field @kotlin.PublishedApi internal long[] metadata;
+    field @kotlin.PublishedApi internal long[] values;
+  }
+
+  public final class FloatLongMapKt {
+    method public static androidx.collection.FloatLongMap emptyFloatLongMap();
+    method public static androidx.collection.FloatLongMap floatLongMapOf();
+    method public static androidx.collection.FloatLongMap floatLongMapOf(kotlin.Pair<java.lang.Float,java.lang.Long>... pairs);
+    method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf();
+    method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf(kotlin.Pair<java.lang.Float,java.lang.Long>... pairs);
+  }
+
+  public abstract sealed class FloatObjectMap<V> {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,java.lang.Boolean> predicate);
+    method public final operator boolean contains(float key);
+    method public final boolean containsKey(float key);
+    method public final boolean containsValue(V value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,kotlin.Unit> block);
+    method @kotlin.PublishedApi internal final inline void forEachIndexed(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super V,kotlin.Unit> block);
+    method public final operator V? get(float key);
+    method public final int getCapacity();
+    method public final V getOrDefault(float key, V defaultValue);
+    method public final inline V getOrElse(float key, kotlin.jvm.functions.Function0<? extends V> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+    field @kotlin.PublishedApi internal float[] keys;
+    field @kotlin.PublishedApi internal long[] metadata;
+    field @kotlin.PublishedApi internal Object![] values;
+  }
+
+  public final class FloatObjectMapKt {
+    method public static <V> androidx.collection.FloatObjectMap<V> emptyFloatObjectMap();
+    method public static <V> androidx.collection.FloatObjectMap<V> floatObjectMapOf();
+    method public static <V> androidx.collection.FloatObjectMap<V> floatObjectMapOf(kotlin.Pair<java.lang.Float,? extends V>... pairs);
+    method public static <V> androidx.collection.MutableFloatObjectMap<V> mutableFloatObjectMapOf();
+    method public static <V> androidx.collection.MutableFloatObjectMap<V> mutableFloatObjectMapOf(kotlin.Pair<java.lang.Float,? extends V>... pairs);
+  }
+
   public abstract sealed class FloatSet {
     method public final inline boolean all(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
     method public final boolean any();
@@ -184,6 +331,80 @@
     method public static androidx.collection.MutableFloatSet mutableFloatSetOf(float... elements);
   }
 
+  public abstract sealed class IntFloatMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final operator boolean contains(int key);
+    method public final boolean containsKey(int key);
+    method public final boolean containsValue(float value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,java.lang.Boolean> predicate);
+    method @kotlin.PublishedApi internal final int findKeyIndex(int key);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,kotlin.Unit> block);
+    method @kotlin.PublishedApi internal final inline void forEachIndexed(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> block);
+    method public final operator float get(int key);
+    method public final int getCapacity();
+    method public final float getOrDefault(int key, float defaultValue);
+    method public final inline float getOrElse(int key, kotlin.jvm.functions.Function0<java.lang.Float> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+    field @kotlin.PublishedApi internal int[] keys;
+    field @kotlin.PublishedApi internal long[] metadata;
+    field @kotlin.PublishedApi internal float[] values;
+  }
+
+  public final class IntFloatMapKt {
+    method public static androidx.collection.IntFloatMap emptyIntFloatMap();
+    method public static androidx.collection.IntFloatMap intFloatMapOf();
+    method public static androidx.collection.IntFloatMap intFloatMapOf(kotlin.Pair<java.lang.Integer,java.lang.Float>... pairs);
+    method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf();
+    method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf(kotlin.Pair<java.lang.Integer,java.lang.Float>... pairs);
+  }
+
+  public abstract sealed class IntIntMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final operator boolean contains(int key);
+    method public final boolean containsKey(int key);
+    method public final boolean containsValue(int value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method @kotlin.PublishedApi internal final int findKeyIndex(int key);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> block);
+    method @kotlin.PublishedApi internal final inline void forEachIndexed(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final operator int get(int key);
+    method public final int getCapacity();
+    method public final int getOrDefault(int key, int defaultValue);
+    method public final inline int getOrElse(int key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+    field @kotlin.PublishedApi internal int[] keys;
+    field @kotlin.PublishedApi internal long[] metadata;
+    field @kotlin.PublishedApi internal int[] values;
+  }
+
+  public final class IntIntMapKt {
+    method public static androidx.collection.IntIntMap emptyIntIntMap();
+    method public static androidx.collection.IntIntMap intIntMapOf();
+    method public static androidx.collection.IntIntMap intIntMapOf(kotlin.Pair<java.lang.Integer,java.lang.Integer>... pairs);
+    method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf();
+    method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf(kotlin.Pair<java.lang.Integer,java.lang.Integer>... pairs);
+  }
+
   public abstract sealed class IntList {
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
@@ -238,6 +459,79 @@
     method public static inline androidx.collection.MutableIntList mutableIntListOf(int... elements);
   }
 
+  public abstract sealed class IntLongMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final operator boolean contains(int key);
+    method public final boolean containsKey(int key);
+    method public final boolean containsValue(long value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,java.lang.Boolean> predicate);
+    method @kotlin.PublishedApi internal final int findKeyIndex(int key);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,kotlin.Unit> block);
+    method @kotlin.PublishedApi internal final inline void forEachIndexed(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Long,kotlin.Unit> block);
+    method public final operator long get(int key);
+    method public final int getCapacity();
+    method public final long getOrDefault(int key, long defaultValue);
+    method public final inline long getOrElse(int key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+    field @kotlin.PublishedApi internal int[] keys;
+    field @kotlin.PublishedApi internal long[] metadata;
+    field @kotlin.PublishedApi internal long[] values;
+  }
+
+  public final class IntLongMapKt {
+    method public static androidx.collection.IntLongMap emptyIntLongMap();
+    method public static androidx.collection.IntLongMap intLongMapOf();
+    method public static androidx.collection.IntLongMap intLongMapOf(kotlin.Pair<java.lang.Integer,java.lang.Long>... pairs);
+    method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf();
+    method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf(kotlin.Pair<java.lang.Integer,java.lang.Long>... pairs);
+  }
+
+  public abstract sealed class IntObjectMap<V> {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,java.lang.Boolean> predicate);
+    method public final operator boolean contains(int key);
+    method public final boolean containsKey(int key);
+    method public final boolean containsValue(V value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,kotlin.Unit> block);
+    method @kotlin.PublishedApi internal final inline void forEachIndexed(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super V,kotlin.Unit> block);
+    method public final operator V? get(int key);
+    method public final int getCapacity();
+    method public final V getOrDefault(int key, V defaultValue);
+    method public final inline V getOrElse(int key, kotlin.jvm.functions.Function0<? extends V> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+    field @kotlin.PublishedApi internal int[] keys;
+    field @kotlin.PublishedApi internal long[] metadata;
+    field @kotlin.PublishedApi internal Object![] values;
+  }
+
+  public final class IntObjectMapKt {
+    method public static <V> androidx.collection.IntObjectMap<V> emptyIntObjectMap();
+    method public static <V> androidx.collection.IntObjectMap<V> intObjectMapOf();
+    method public static <V> androidx.collection.IntObjectMap<V> intObjectMapOf(kotlin.Pair<java.lang.Integer,? extends V>... pairs);
+    method public static <V> androidx.collection.MutableIntObjectMap<V> mutableIntObjectMapOf();
+    method public static <V> androidx.collection.MutableIntObjectMap<V> mutableIntObjectMapOf(kotlin.Pair<java.lang.Integer,? extends V>... pairs);
+  }
+
   public abstract sealed class IntSet {
     method public final inline boolean all(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
     method public final boolean any();
@@ -274,6 +568,80 @@
     method public static androidx.collection.MutableIntSet mutableIntSetOf(int... elements);
   }
 
+  public abstract sealed class LongFloatMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final operator boolean contains(long key);
+    method public final boolean containsKey(long key);
+    method public final boolean containsValue(float value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,java.lang.Boolean> predicate);
+    method @kotlin.PublishedApi internal final int findKeyIndex(long key);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,kotlin.Unit> block);
+    method @kotlin.PublishedApi internal final inline void forEachIndexed(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Long,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> block);
+    method public final operator float get(long key);
+    method public final int getCapacity();
+    method public final float getOrDefault(long key, float defaultValue);
+    method public final inline float getOrElse(long key, kotlin.jvm.functions.Function0<java.lang.Float> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+    field @kotlin.PublishedApi internal long[] keys;
+    field @kotlin.PublishedApi internal long[] metadata;
+    field @kotlin.PublishedApi internal float[] values;
+  }
+
+  public final class LongFloatMapKt {
+    method public static androidx.collection.LongFloatMap emptyLongFloatMap();
+    method public static androidx.collection.LongFloatMap longFloatMapOf();
+    method public static androidx.collection.LongFloatMap longFloatMapOf(kotlin.Pair<java.lang.Long,java.lang.Float>... pairs);
+    method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf();
+    method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf(kotlin.Pair<java.lang.Long,java.lang.Float>... pairs);
+  }
+
+  public abstract sealed class LongIntMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final operator boolean contains(long key);
+    method public final boolean containsKey(long key);
+    method public final boolean containsValue(int value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method @kotlin.PublishedApi internal final int findKeyIndex(long key);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,kotlin.Unit> block);
+    method @kotlin.PublishedApi internal final inline void forEachIndexed(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Long,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final operator int get(long key);
+    method public final int getCapacity();
+    method public final int getOrDefault(long key, int defaultValue);
+    method public final inline int getOrElse(long key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+    field @kotlin.PublishedApi internal long[] keys;
+    field @kotlin.PublishedApi internal long[] metadata;
+    field @kotlin.PublishedApi internal int[] values;
+  }
+
+  public final class LongIntMapKt {
+    method public static androidx.collection.LongIntMap emptyLongIntMap();
+    method public static androidx.collection.LongIntMap longIntMapOf();
+    method public static androidx.collection.LongIntMap longIntMapOf(kotlin.Pair<java.lang.Long,java.lang.Integer>... pairs);
+    method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf();
+    method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf(kotlin.Pair<java.lang.Long,java.lang.Integer>... pairs);
+  }
+
   public abstract sealed class LongList {
     method public final boolean any();
     method public final inline boolean any(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
@@ -328,6 +696,79 @@
     method public static inline androidx.collection.MutableLongList mutableLongListOf(long... elements);
   }
 
+  public abstract sealed class LongLongMap {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final operator boolean contains(long key);
+    method public final boolean containsKey(long key);
+    method public final boolean containsValue(long value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,java.lang.Boolean> predicate);
+    method @kotlin.PublishedApi internal final int findKeyIndex(long key);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,kotlin.Unit> block);
+    method @kotlin.PublishedApi internal final inline void forEachIndexed(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Long,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Long,kotlin.Unit> block);
+    method public final operator long get(long key);
+    method public final int getCapacity();
+    method public final long getOrDefault(long key, long defaultValue);
+    method public final inline long getOrElse(long key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+    field @kotlin.PublishedApi internal long[] keys;
+    field @kotlin.PublishedApi internal long[] metadata;
+    field @kotlin.PublishedApi internal long[] values;
+  }
+
+  public final class LongLongMapKt {
+    method public static androidx.collection.LongLongMap emptyLongLongMap();
+    method public static androidx.collection.LongLongMap longLongMapOf();
+    method public static androidx.collection.LongLongMap longLongMapOf(kotlin.Pair<java.lang.Long,java.lang.Long>... pairs);
+    method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf();
+    method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf(kotlin.Pair<java.lang.Long,java.lang.Long>... pairs);
+  }
+
+  public abstract sealed class LongObjectMap<V> {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,java.lang.Boolean> predicate);
+    method public final operator boolean contains(long key);
+    method public final boolean containsKey(long key);
+    method public final boolean containsValue(V value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,java.lang.Boolean> predicate);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,kotlin.Unit> block);
+    method @kotlin.PublishedApi internal final inline void forEachIndexed(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super java.lang.Long,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super V,kotlin.Unit> block);
+    method public final operator V? get(long key);
+    method public final int getCapacity();
+    method public final V getOrDefault(long key, V defaultValue);
+    method public final inline V getOrElse(long key, kotlin.jvm.functions.Function0<? extends V> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+    field @kotlin.PublishedApi internal long[] keys;
+    field @kotlin.PublishedApi internal long[] metadata;
+    field @kotlin.PublishedApi internal Object![] values;
+  }
+
+  public final class LongObjectMapKt {
+    method public static <V> androidx.collection.LongObjectMap<V> emptyLongObjectMap();
+    method public static <V> androidx.collection.LongObjectMap<V> longObjectMapOf();
+    method public static <V> androidx.collection.LongObjectMap<V> longObjectMapOf(kotlin.Pair<java.lang.Long,? extends V>... pairs);
+    method public static <V> androidx.collection.MutableLongObjectMap<V> mutableLongObjectMapOf();
+    method public static <V> androidx.collection.MutableLongObjectMap<V> mutableLongObjectMapOf(kotlin.Pair<java.lang.Long,? extends V>... pairs);
+  }
+
   public abstract sealed class LongSet {
     method public final inline boolean all(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
     method public final boolean any();
@@ -431,6 +872,48 @@
     method public static inline <K, V> androidx.collection.LruCache<K,V> lruCache(int maxSize, optional kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf, optional kotlin.jvm.functions.Function1<? super K,? extends V> create, optional kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved);
   }
 
+  public final class MutableFloatFloatMap extends androidx.collection.FloatFloatMap {
+    ctor public MutableFloatFloatMap(optional int initialCapacity);
+    method public void clear();
+    method public inline float getOrPut(float key, kotlin.jvm.functions.Function0<java.lang.Float> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.FloatList keys);
+    method public inline operator void minusAssign(androidx.collection.FloatSet keys);
+    method public inline operator void minusAssign(float key);
+    method public inline operator void minusAssign(float[] keys);
+    method public inline operator void plusAssign(androidx.collection.FloatFloatMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Float,java.lang.Float> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Float,java.lang.Float>![] pairs);
+    method public void put(float key, float value);
+    method public void putAll(androidx.collection.FloatFloatMap from);
+    method public void putAll(kotlin.Pair<java.lang.Float,java.lang.Float>![] pairs);
+    method public void remove(float key);
+    method public boolean remove(float key, float value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public operator void set(float key, float value);
+    method public int trim();
+  }
+
+  public final class MutableFloatIntMap extends androidx.collection.FloatIntMap {
+    ctor public MutableFloatIntMap(optional int initialCapacity);
+    method public void clear();
+    method public inline int getOrPut(float key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.FloatList keys);
+    method public inline operator void minusAssign(androidx.collection.FloatSet keys);
+    method public inline operator void minusAssign(float key);
+    method public inline operator void minusAssign(float[] keys);
+    method public inline operator void plusAssign(androidx.collection.FloatIntMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Float,java.lang.Integer> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Float,java.lang.Integer>![] pairs);
+    method public void put(float key, int value);
+    method public void putAll(androidx.collection.FloatIntMap from);
+    method public void putAll(kotlin.Pair<java.lang.Float,java.lang.Integer>![] pairs);
+    method public void remove(float key);
+    method public boolean remove(float key, int value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public operator void set(float key, int value);
+    method public int trim();
+  }
+
   public final class MutableFloatList extends androidx.collection.FloatList {
     ctor public MutableFloatList(optional int initialCapacity);
     method public boolean add(float element);
@@ -462,6 +945,48 @@
     property public final inline int capacity;
   }
 
+  public final class MutableFloatLongMap extends androidx.collection.FloatLongMap {
+    ctor public MutableFloatLongMap(optional int initialCapacity);
+    method public void clear();
+    method public inline long getOrPut(float key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.FloatList keys);
+    method public inline operator void minusAssign(androidx.collection.FloatSet keys);
+    method public inline operator void minusAssign(float key);
+    method public inline operator void minusAssign(float[] keys);
+    method public inline operator void plusAssign(androidx.collection.FloatLongMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Float,java.lang.Long> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Float,java.lang.Long>![] pairs);
+    method public void put(float key, long value);
+    method public void putAll(androidx.collection.FloatLongMap from);
+    method public void putAll(kotlin.Pair<java.lang.Float,java.lang.Long>![] pairs);
+    method public void remove(float key);
+    method public boolean remove(float key, long value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public operator void set(float key, long value);
+    method public int trim();
+  }
+
+  public final class MutableFloatObjectMap<V> extends androidx.collection.FloatObjectMap<V> {
+    ctor public MutableFloatObjectMap(optional int initialCapacity);
+    method public void clear();
+    method public inline V getOrPut(float key, kotlin.jvm.functions.Function0<? extends V> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.FloatList keys);
+    method public inline operator void minusAssign(androidx.collection.FloatSet keys);
+    method public inline operator void minusAssign(float key);
+    method public inline operator void minusAssign(float[] keys);
+    method public inline operator void plusAssign(androidx.collection.FloatObjectMap<V> from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Float,? extends V> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Float,? extends V>![] pairs);
+    method public V? put(float key, V value);
+    method public void putAll(androidx.collection.FloatObjectMap<V> from);
+    method public void putAll(kotlin.Pair<java.lang.Float,? extends V>![] pairs);
+    method public V? remove(float key);
+    method public boolean remove(float key, V value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,java.lang.Boolean> predicate);
+    method public operator void set(float key, V value);
+    method public int trim();
+  }
+
   public final class MutableFloatSet extends androidx.collection.FloatSet {
     ctor public MutableFloatSet(optional int initialCapacity);
     method public boolean add(float element);
@@ -480,6 +1005,48 @@
     method @IntRange(from=0L) public int trim();
   }
 
+  public final class MutableIntFloatMap extends androidx.collection.IntFloatMap {
+    ctor public MutableIntFloatMap(optional int initialCapacity);
+    method public void clear();
+    method public inline float getOrPut(int key, kotlin.jvm.functions.Function0<java.lang.Float> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.IntList keys);
+    method public inline operator void minusAssign(androidx.collection.IntSet keys);
+    method public inline operator void minusAssign(int key);
+    method public inline operator void minusAssign(int[] keys);
+    method public inline operator void plusAssign(androidx.collection.IntFloatMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Integer,java.lang.Float> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Integer,java.lang.Float>![] pairs);
+    method public void put(int key, float value);
+    method public void putAll(androidx.collection.IntFloatMap from);
+    method public void putAll(kotlin.Pair<java.lang.Integer,java.lang.Float>![] pairs);
+    method public void remove(int key);
+    method public boolean remove(int key, float value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public operator void set(int key, float value);
+    method public int trim();
+  }
+
+  public final class MutableIntIntMap extends androidx.collection.IntIntMap {
+    ctor public MutableIntIntMap(optional int initialCapacity);
+    method public void clear();
+    method public inline int getOrPut(int key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.IntList keys);
+    method public inline operator void minusAssign(androidx.collection.IntSet keys);
+    method public inline operator void minusAssign(int key);
+    method public inline operator void minusAssign(int[] keys);
+    method public inline operator void plusAssign(androidx.collection.IntIntMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Integer,java.lang.Integer> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Integer,java.lang.Integer>![] pairs);
+    method public void put(int key, int value);
+    method public void putAll(androidx.collection.IntIntMap from);
+    method public void putAll(kotlin.Pair<java.lang.Integer,java.lang.Integer>![] pairs);
+    method public void remove(int key);
+    method public boolean remove(int key, int value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public operator void set(int key, int value);
+    method public int trim();
+  }
+
   public final class MutableIntList extends androidx.collection.IntList {
     ctor public MutableIntList(optional int initialCapacity);
     method public boolean add(int element);
@@ -511,6 +1078,48 @@
     property public final inline int capacity;
   }
 
+  public final class MutableIntLongMap extends androidx.collection.IntLongMap {
+    ctor public MutableIntLongMap(optional int initialCapacity);
+    method public void clear();
+    method public inline long getOrPut(int key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.IntList keys);
+    method public inline operator void minusAssign(androidx.collection.IntSet keys);
+    method public inline operator void minusAssign(int key);
+    method public inline operator void minusAssign(int[] keys);
+    method public inline operator void plusAssign(androidx.collection.IntLongMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Integer,java.lang.Long> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Integer,java.lang.Long>![] pairs);
+    method public void put(int key, long value);
+    method public void putAll(androidx.collection.IntLongMap from);
+    method public void putAll(kotlin.Pair<java.lang.Integer,java.lang.Long>![] pairs);
+    method public void remove(int key);
+    method public boolean remove(int key, long value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public operator void set(int key, long value);
+    method public int trim();
+  }
+
+  public final class MutableIntObjectMap<V> extends androidx.collection.IntObjectMap<V> {
+    ctor public MutableIntObjectMap(optional int initialCapacity);
+    method public void clear();
+    method public inline V getOrPut(int key, kotlin.jvm.functions.Function0<? extends V> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.IntList keys);
+    method public inline operator void minusAssign(androidx.collection.IntSet keys);
+    method public inline operator void minusAssign(int key);
+    method public inline operator void minusAssign(int[] keys);
+    method public inline operator void plusAssign(androidx.collection.IntObjectMap<V> from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Integer,? extends V> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Integer,? extends V>![] pairs);
+    method public V? put(int key, V value);
+    method public void putAll(androidx.collection.IntObjectMap<V> from);
+    method public void putAll(kotlin.Pair<java.lang.Integer,? extends V>![] pairs);
+    method public V? remove(int key);
+    method public boolean remove(int key, V value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,java.lang.Boolean> predicate);
+    method public operator void set(int key, V value);
+    method public int trim();
+  }
+
   public final class MutableIntSet extends androidx.collection.IntSet {
     ctor public MutableIntSet(optional int initialCapacity);
     method public boolean add(int element);
@@ -529,6 +1138,48 @@
     method @IntRange(from=0L) public int trim();
   }
 
+  public final class MutableLongFloatMap extends androidx.collection.LongFloatMap {
+    ctor public MutableLongFloatMap(optional int initialCapacity);
+    method public void clear();
+    method public inline float getOrPut(long key, kotlin.jvm.functions.Function0<java.lang.Float> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.LongList keys);
+    method public inline operator void minusAssign(androidx.collection.LongSet keys);
+    method public inline operator void minusAssign(long key);
+    method public inline operator void minusAssign(long[] keys);
+    method public inline operator void plusAssign(androidx.collection.LongFloatMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Long,java.lang.Float> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Long,java.lang.Float>![] pairs);
+    method public void put(long key, float value);
+    method public void putAll(androidx.collection.LongFloatMap from);
+    method public void putAll(kotlin.Pair<java.lang.Long,java.lang.Float>![] pairs);
+    method public void remove(long key);
+    method public boolean remove(long key, float value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public operator void set(long key, float value);
+    method public int trim();
+  }
+
+  public final class MutableLongIntMap extends androidx.collection.LongIntMap {
+    ctor public MutableLongIntMap(optional int initialCapacity);
+    method public void clear();
+    method public inline int getOrPut(long key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.LongList keys);
+    method public inline operator void minusAssign(androidx.collection.LongSet keys);
+    method public inline operator void minusAssign(long key);
+    method public inline operator void minusAssign(long[] keys);
+    method public inline operator void plusAssign(androidx.collection.LongIntMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Long,java.lang.Integer> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Long,java.lang.Integer>![] pairs);
+    method public void put(long key, int value);
+    method public void putAll(androidx.collection.LongIntMap from);
+    method public void putAll(kotlin.Pair<java.lang.Long,java.lang.Integer>![] pairs);
+    method public void remove(long key);
+    method public boolean remove(long key, int value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public operator void set(long key, int value);
+    method public int trim();
+  }
+
   public final class MutableLongList extends androidx.collection.LongList {
     ctor public MutableLongList(optional int initialCapacity);
     method public void add(@IntRange(from=0L) int index, long element);
@@ -560,6 +1211,48 @@
     property public final inline int capacity;
   }
 
+  public final class MutableLongLongMap extends androidx.collection.LongLongMap {
+    ctor public MutableLongLongMap(optional int initialCapacity);
+    method public void clear();
+    method public inline long getOrPut(long key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.LongList keys);
+    method public inline operator void minusAssign(androidx.collection.LongSet keys);
+    method public inline operator void minusAssign(long key);
+    method public inline operator void minusAssign(long[] keys);
+    method public inline operator void plusAssign(androidx.collection.LongLongMap from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Long,java.lang.Long> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Long,java.lang.Long>![] pairs);
+    method public void put(long key, long value);
+    method public void putAll(androidx.collection.LongLongMap from);
+    method public void putAll(kotlin.Pair<java.lang.Long,java.lang.Long>![] pairs);
+    method public void remove(long key);
+    method public boolean remove(long key, long value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public operator void set(long key, long value);
+    method public int trim();
+  }
+
+  public final class MutableLongObjectMap<V> extends androidx.collection.LongObjectMap<V> {
+    ctor public MutableLongObjectMap(optional int initialCapacity);
+    method public void clear();
+    method public inline V getOrPut(long key, kotlin.jvm.functions.Function0<? extends V> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.LongList keys);
+    method public inline operator void minusAssign(androidx.collection.LongSet keys);
+    method public inline operator void minusAssign(long key);
+    method public inline operator void minusAssign(long[] keys);
+    method public inline operator void plusAssign(androidx.collection.LongObjectMap<V> from);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Long,? extends V> pair);
+    method public inline operator void plusAssign(kotlin.Pair<java.lang.Long,? extends V>![] pairs);
+    method public V? put(long key, V value);
+    method public void putAll(androidx.collection.LongObjectMap<V> from);
+    method public void putAll(kotlin.Pair<java.lang.Long,? extends V>![] pairs);
+    method public V? remove(long key);
+    method public boolean remove(long key, V value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,java.lang.Boolean> predicate);
+    method public operator void set(long key, V value);
+    method public int trim();
+  }
+
   public final class MutableLongSet extends androidx.collection.LongSet {
     ctor public MutableLongSet(optional int initialCapacity);
     method public boolean add(long element);
@@ -578,6 +1271,124 @@
     method @IntRange(from=0L) public int trim();
   }
 
+  public final class MutableObjectFloatMap<K> extends androidx.collection.ObjectFloatMap<K> {
+    ctor public MutableObjectFloatMap(optional int initialCapacity);
+    method public void clear();
+    method public inline float getOrPut(K key, kotlin.jvm.functions.Function0<java.lang.Float> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.ScatterSet<K> keys);
+    method public inline operator void minusAssign(Iterable<? extends K> keys);
+    method public inline operator void minusAssign(K key);
+    method public inline operator void minusAssign(K![] keys);
+    method public inline operator void minusAssign(kotlin.sequences.Sequence<? extends K> keys);
+    method public inline operator void plusAssign(androidx.collection.ObjectFloatMap<K> from);
+    method public inline operator void plusAssign(kotlin.Pair<? extends K,java.lang.Float> pair);
+    method public inline operator void plusAssign(kotlin.Pair<? extends K,java.lang.Float>![] pairs);
+    method public void put(K key, float value);
+    method public void putAll(androidx.collection.ObjectFloatMap<K> from);
+    method public void putAll(kotlin.Pair<? extends K,java.lang.Float>![] pairs);
+    method public void remove(K key);
+    method public boolean remove(K key, float value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public operator void set(K key, float value);
+    method public int trim();
+  }
+
+  public final class MutableObjectIntMap<K> extends androidx.collection.ObjectIntMap<K> {
+    ctor public MutableObjectIntMap(optional int initialCapacity);
+    method public void clear();
+    method public inline int getOrPut(K key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.ScatterSet<K> keys);
+    method public inline operator void minusAssign(Iterable<? extends K> keys);
+    method public inline operator void minusAssign(K key);
+    method public inline operator void minusAssign(K![] keys);
+    method public inline operator void minusAssign(kotlin.sequences.Sequence<? extends K> keys);
+    method public inline operator void plusAssign(androidx.collection.ObjectIntMap<K> from);
+    method public inline operator void plusAssign(kotlin.Pair<? extends K,java.lang.Integer> pair);
+    method public inline operator void plusAssign(kotlin.Pair<? extends K,java.lang.Integer>![] pairs);
+    method public void put(K key, int value);
+    method public void putAll(androidx.collection.ObjectIntMap<K> from);
+    method public void putAll(kotlin.Pair<? extends K,java.lang.Integer>![] pairs);
+    method public void remove(K key);
+    method public boolean remove(K key, int value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public operator void set(K key, int value);
+    method public int trim();
+  }
+
+  public final class MutableObjectList<E> extends androidx.collection.ObjectList<E> {
+    ctor public MutableObjectList(optional int initialCapacity);
+    method public boolean add(E element);
+    method public void add(@IntRange(from=0L) int index, E element);
+    method public boolean addAll(androidx.collection.ObjectList<E> elements);
+    method public boolean addAll(androidx.collection.ScatterSet<E> elements);
+    method public boolean addAll(E![] elements);
+    method public boolean addAll(@IntRange(from=0L) int index, androidx.collection.ObjectList<E> elements);
+    method public boolean addAll(@IntRange(from=0L) int index, E![] elements);
+    method public boolean addAll(@IntRange(from=0L) int index, java.util.Collection<? extends E> elements);
+    method public boolean addAll(Iterable<? extends E> elements);
+    method public boolean addAll(java.util.List<? extends E> elements);
+    method public boolean addAll(kotlin.sequences.Sequence<? extends E> elements);
+    method public java.util.List<E> asList();
+    method public java.util.List<E> asMutableList();
+    method public void clear();
+    method public void ensureCapacity(int capacity);
+    method public inline int getCapacity();
+    method public operator void minusAssign(androidx.collection.ObjectList<E> elements);
+    method public operator void minusAssign(androidx.collection.ScatterSet<E> elements);
+    method public inline operator void minusAssign(E element);
+    method public operator void minusAssign(E![] elements);
+    method public operator void minusAssign(Iterable<? extends E> elements);
+    method public operator void minusAssign(java.util.List<? extends E> elements);
+    method public operator void minusAssign(kotlin.sequences.Sequence<? extends E> elements);
+    method public operator void plusAssign(androidx.collection.ObjectList<E> elements);
+    method public operator void plusAssign(androidx.collection.ScatterSet<E> elements);
+    method public inline operator void plusAssign(E element);
+    method public operator void plusAssign(E![] elements);
+    method public operator void plusAssign(Iterable<? extends E> elements);
+    method public operator void plusAssign(java.util.List<? extends E> elements);
+    method public operator void plusAssign(kotlin.sequences.Sequence<? extends E> elements);
+    method public boolean remove(E element);
+    method public boolean removeAll(androidx.collection.ObjectList<E> elements);
+    method public boolean removeAll(androidx.collection.ScatterSet<E> elements);
+    method public boolean removeAll(E![] elements);
+    method public boolean removeAll(Iterable<? extends E> elements);
+    method public boolean removeAll(java.util.List<? extends E> elements);
+    method public boolean removeAll(kotlin.sequences.Sequence<? extends E> elements);
+    method public E removeAt(@IntRange(from=0L) int index);
+    method public inline void removeIf(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public void removeRange(@IntRange(from=0L) int start, @IntRange(from=0L) int end);
+    method public boolean retainAll(androidx.collection.ObjectList<E> elements);
+    method public boolean retainAll(E![] elements);
+    method public boolean retainAll(Iterable<? extends E> elements);
+    method public boolean retainAll(java.util.Collection<? extends E> elements);
+    method public boolean retainAll(kotlin.sequences.Sequence<? extends E> elements);
+    method public operator E set(@IntRange(from=0L) int index, E element);
+    method public void trim(optional int minCapacity);
+    property public final inline int capacity;
+  }
+
+  public final class MutableObjectLongMap<K> extends androidx.collection.ObjectLongMap<K> {
+    ctor public MutableObjectLongMap(optional int initialCapacity);
+    method public void clear();
+    method public inline long getOrPut(K key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.ScatterSet<K> keys);
+    method public inline operator void minusAssign(Iterable<? extends K> keys);
+    method public inline operator void minusAssign(K key);
+    method public inline operator void minusAssign(K![] keys);
+    method public inline operator void minusAssign(kotlin.sequences.Sequence<? extends K> keys);
+    method public inline operator void plusAssign(androidx.collection.ObjectLongMap<K> from);
+    method public inline operator void plusAssign(kotlin.Pair<? extends K,java.lang.Long> pair);
+    method public inline operator void plusAssign(kotlin.Pair<? extends K,java.lang.Long>![] pairs);
+    method public void put(K key, long value);
+    method public void putAll(androidx.collection.ObjectLongMap<K> from);
+    method public void putAll(kotlin.Pair<? extends K,java.lang.Long>![] pairs);
+    method public void remove(K key);
+    method public boolean remove(K key, long value);
+    method public void removeIf(kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public operator void set(K key, long value);
+    method public int trim();
+  }
+
   public final class MutableScatterMap<K, V> extends androidx.collection.ScatterMap<K,V> {
     ctor public MutableScatterMap(optional int initialCapacity);
     method public java.util.Map<K,V> asMutableMap();
@@ -635,6 +1446,179 @@
     method @IntRange(from=0L) public int trim();
   }
 
+  public abstract sealed class ObjectFloatMap<K> {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,java.lang.Boolean> predicate);
+    method public final operator boolean contains(K key);
+    method public final boolean containsKey(K key);
+    method public final boolean containsValue(float value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,java.lang.Boolean> predicate);
+    method @kotlin.PublishedApi internal final int findKeyIndex(K key);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,kotlin.Unit> block);
+    method @kotlin.PublishedApi internal final inline void forEachIndexed(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super K,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> block);
+    method public final operator float get(K key);
+    method public final int getCapacity();
+    method public final float getOrDefault(K key, float defaultValue);
+    method public final inline float getOrElse(K key, kotlin.jvm.functions.Function0<java.lang.Float> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+    field @kotlin.PublishedApi internal Object![] keys;
+    field @kotlin.PublishedApi internal long[] metadata;
+    field @kotlin.PublishedApi internal float[] values;
+  }
+
+  public final class ObjectFloatMapKt {
+    method public static <K> androidx.collection.ObjectFloatMap<K> emptyObjectFloatMap();
+    method public static <K> androidx.collection.MutableObjectFloatMap<K> mutableObjectFloatMapOf();
+    method public static <K> androidx.collection.MutableObjectFloatMap<K> mutableObjectFloatMapOf(kotlin.Pair<? extends K,java.lang.Float>... pairs);
+    method public static <K> androidx.collection.ObjectFloatMap<K> objectFloatMap();
+    method public static <K> androidx.collection.ObjectFloatMap<K> objectFloatMapOf(kotlin.Pair<? extends K,java.lang.Float>... pairs);
+  }
+
+  public abstract sealed class ObjectIntMap<K> {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method public final operator boolean contains(K key);
+    method public final boolean containsKey(K key);
+    method public final boolean containsValue(int value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,java.lang.Boolean> predicate);
+    method @kotlin.PublishedApi internal final int findKeyIndex(K key);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,kotlin.Unit> block);
+    method @kotlin.PublishedApi internal final inline void forEachIndexed(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super K,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final operator int get(K key);
+    method public final int getCapacity();
+    method public final int getOrDefault(K key, int defaultValue);
+    method public final inline int getOrElse(K key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+    field @kotlin.PublishedApi internal Object![] keys;
+    field @kotlin.PublishedApi internal long[] metadata;
+    field @kotlin.PublishedApi internal int[] values;
+  }
+
+  public final class ObjectIntMapKt {
+    method public static <K> androidx.collection.ObjectIntMap<K> emptyObjectIntMap();
+    method public static <K> androidx.collection.MutableObjectIntMap<K> mutableObjectIntMapOf();
+    method public static <K> androidx.collection.MutableObjectIntMap<K> mutableObjectIntMapOf(kotlin.Pair<? extends K,java.lang.Integer>... pairs);
+    method public static <K> androidx.collection.ObjectIntMap<K> objectIntMap();
+    method public static <K> androidx.collection.ObjectIntMap<K> objectIntMapOf(kotlin.Pair<? extends K,java.lang.Integer>... pairs);
+  }
+
+  public abstract sealed class ObjectList<E> {
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public abstract java.util.List<E> asList();
+    method public final operator boolean contains(E element);
+    method public final boolean containsAll(androidx.collection.ObjectList<E> elements);
+    method public final boolean containsAll(E![] elements);
+    method public final boolean containsAll(Iterable<? extends E> elements);
+    method public final boolean containsAll(java.util.List<? extends E> elements);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final E elementAt(@IntRange(from=0L) int index);
+    method public final inline E elementAtOrElse(@IntRange(from=0L) int index, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends E> defaultValue);
+    method public final E first();
+    method public final inline E first(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final inline E? firstOrNull();
+    method public final inline E? firstOrNull(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final inline <R> R fold(R initial, kotlin.jvm.functions.Function2<? super R,? super E,? extends R> operation);
+    method public final inline <R> R foldIndexed(R initial, kotlin.jvm.functions.Function3<? super java.lang.Integer,? super R,? super E,? extends R> operation);
+    method public final inline <R> R foldRight(R initial, kotlin.jvm.functions.Function2<? super E,? super R,? extends R> operation);
+    method public final inline <R> R foldRightIndexed(R initial, kotlin.jvm.functions.Function3<? super java.lang.Integer,? super E,? super R,? extends R> operation);
+    method public final inline void forEach(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+    method public final inline void forEachIndexed(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super E,kotlin.Unit> block);
+    method public final inline void forEachReversed(kotlin.jvm.functions.Function1<? super E,kotlin.Unit> block);
+    method public final inline void forEachReversedIndexed(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super E,kotlin.Unit> block);
+    method public final operator E get(@IntRange(from=0L) int index);
+    method public final inline kotlin.ranges.IntRange getIndices();
+    method @IntRange(from=-1L) public final inline int getLastIndex();
+    method @IntRange(from=0L) public final int getSize();
+    method public final int indexOf(E element);
+    method public final inline int indexOfFirst(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final E last();
+    method public final inline E last(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final int lastIndexOf(E element);
+    method public final inline E? lastOrNull();
+    method public final inline E? lastOrNull(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    method public final boolean none();
+    method public final inline boolean reversedAny(kotlin.jvm.functions.Function1<? super E,java.lang.Boolean> predicate);
+    property public final inline kotlin.ranges.IntRange indices;
+    property @IntRange(from=-1L) public final inline int lastIndex;
+    property @IntRange(from=0L) public final int size;
+    field @kotlin.PublishedApi internal int _size;
+    field @kotlin.PublishedApi internal Object![] content;
+  }
+
+  public final class ObjectListKt {
+    method public static <E> androidx.collection.ObjectList<E> emptyObjectList();
+    method public static inline <E> androidx.collection.MutableObjectList<E> mutableObjectListOf();
+    method public static <E> androidx.collection.MutableObjectList<E> mutableObjectListOf(E element1);
+    method public static <E> androidx.collection.MutableObjectList<E> mutableObjectListOf(E element1, E element2);
+    method public static <E> androidx.collection.MutableObjectList<E> mutableObjectListOf(E element1, E element2, E element3);
+    method public static inline <E> androidx.collection.MutableObjectList<E> mutableObjectListOf(E?... elements);
+    method public static <E> androidx.collection.ObjectList<E> objectListOf();
+    method public static <E> androidx.collection.ObjectList<E> objectListOf(E element1);
+    method public static <E> androidx.collection.ObjectList<E> objectListOf(E element1, E element2);
+    method public static <E> androidx.collection.ObjectList<E> objectListOf(E element1, E element2, E element3);
+    method public static <E> androidx.collection.ObjectList<E> objectListOf(E?... elements);
+  }
+
+  public abstract sealed class ObjectLongMap<K> {
+    method public final inline boolean all(kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final boolean any();
+    method public final inline boolean any(kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,java.lang.Boolean> predicate);
+    method public final operator boolean contains(K key);
+    method public final boolean containsKey(K key);
+    method public final boolean containsValue(long value);
+    method public final int count();
+    method public final inline int count(kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,java.lang.Boolean> predicate);
+    method @kotlin.PublishedApi internal final int findKeyIndex(K key);
+    method public final inline void forEach(kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,kotlin.Unit> block);
+    method @kotlin.PublishedApi internal final inline void forEachIndexed(kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> block);
+    method public final inline void forEachKey(kotlin.jvm.functions.Function1<? super K,kotlin.Unit> block);
+    method public final inline void forEachValue(kotlin.jvm.functions.Function1<? super java.lang.Long,kotlin.Unit> block);
+    method public final operator long get(K key);
+    method public final int getCapacity();
+    method public final long getOrDefault(K key, long defaultValue);
+    method public final inline long getOrElse(K key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+    method public final int getSize();
+    method public final boolean isEmpty();
+    method public final boolean isNotEmpty();
+    method public final boolean none();
+    property public final int capacity;
+    property public final int size;
+    field @kotlin.PublishedApi internal Object![] keys;
+    field @kotlin.PublishedApi internal long[] metadata;
+    field @kotlin.PublishedApi internal long[] values;
+  }
+
+  public final class ObjectLongMapKt {
+    method public static <K> androidx.collection.ObjectLongMap<K> emptyObjectLongMap();
+    method public static <K> androidx.collection.MutableObjectLongMap<K> mutableObjectLongMapOf();
+    method public static <K> androidx.collection.MutableObjectLongMap<K> mutableObjectLongMapOf(kotlin.Pair<? extends K,java.lang.Long>... pairs);
+    method public static <K> androidx.collection.ObjectLongMap<K> objectLongMap();
+    method public static <K> androidx.collection.ObjectLongMap<K> objectLongMapOf(kotlin.Pair<? extends K,java.lang.Long>... pairs);
+  }
+
   @kotlin.jvm.JvmInline public final value class PairFloatFloat {
     ctor public PairFloatFloat(float first, float second);
     method public inline operator float component1();
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatMap.kt
new file mode 100644
index 0000000..bca375a
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatMap.kt
@@ -0,0 +1,837 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyFloatFloatMap = MutableFloatFloatMap(0)
+
+/**
+ * Returns an empty, read-only [FloatFloatMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun emptyFloatFloatMap(): FloatFloatMap = EmptyFloatFloatMap
+
+/**
+ * Returns a new [MutableFloatFloatMap].
+ */
+public fun floatFloatMapOf(): FloatFloatMap = EmptyFloatFloatMap
+
+/**
+ * Returns a new [FloatFloatMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Float] key and [Float] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun floatFloatMapOf(vararg pairs: Pair<Float, Float>): FloatFloatMap =
+    MutableFloatFloatMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutableFloatFloatMap].
+ */
+public fun mutableFloatFloatMapOf(): MutableFloatFloatMap = MutableFloatFloatMap()
+
+/**
+ * Returns a new [MutableFloatFloatMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Float] key and [Float] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun mutableFloatFloatMapOf(vararg pairs: Pair<Float, Float>): MutableFloatFloatMap =
+    MutableFloatFloatMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [FloatFloatMap] is a container with a [Map]-like interface for
+ * [Float] primitive keys and [Float] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutableFloatFloatMap].
+ *
+ * @see [MutableFloatFloatMap]
+ * @see [ScatterMap]
+ */
+public sealed class FloatFloatMap {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: FloatArray = EmptyFloatArray
+
+    @PublishedApi
+    @JvmField
+    internal var values: FloatArray = EmptyFloatArray
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key].
+     * @throws NoSuchElementException if [key] is not in the map
+     */
+    public operator fun get(key: Float): Float {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            throw NoSuchElementException("Cannot find value for key $key")
+        }
+        return values[index]
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: Float, defaultValue: Float): Float {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: Float, defaultValue: () -> Float): Float {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            return defaultValue()
+        }
+        return values[index]
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: Float, value: Float) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            block(k[index], v[index])
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: Float) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            block(k[index])
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: Float) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            block(v[index])
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (Float, Float) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (Float, Float) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (Float, Float) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: Float): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: Float): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: Float): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [FloatFloatMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is FloatFloatMap) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        forEach { key, value ->
+            if (value != other[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(key)
+            s.append("=")
+            s.append(value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    @PublishedApi
+    internal fun findKeyIndex(key: Float): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutableFloatFloatMap] is a container with a [MutableMap]-like interface for
+ * [Float] primitive keys and [Float] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutableFloatFloatMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see MutableScatterMap
+ */
+public class MutableFloatFloatMap(
+    initialCapacity: Int = DefaultScatterCapacity
+) : FloatFloatMap() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = FloatArray(newCapacity)
+        values = FloatArray(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: Float, defaultValue: () -> Float): Float {
+        val index = findKeyIndex(key)
+        return if (index < 0) {
+            val defValue = defaultValue()
+            put(key, defValue)
+            defValue
+        } else {
+            values[index]
+        }
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: Float, value: Float) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations. Return the previous value associated with the [key],
+     * or `null` if the key was not present in the map.
+     */
+    public fun put(key: Float, value: Float) {
+        set(key, value)
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Float] key and [Float] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<Pair<Float, Float>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: FloatFloatMap) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that [pair] allocated and both the [Float] key and [Float] value are
+     * boxed. Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<Float, Float>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Float] key and [Float] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<Pair<Float, Float>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: FloatFloatMap): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public fun remove(key: Float) {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            removeValueAt(index)
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: Float, value: Float): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (Float, Float) -> Boolean) {
+        forEachIndexed { index ->
+            if (predicate(keys[index], values[index])) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: Float) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: FloatArray) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: FloatSet) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: FloatList) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int) {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: Float): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableFloatFloatMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatIntMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatIntMap.kt
new file mode 100644
index 0000000..29adea3
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatIntMap.kt
@@ -0,0 +1,837 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyFloatIntMap = MutableFloatIntMap(0)
+
+/**
+ * Returns an empty, read-only [FloatIntMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun emptyFloatIntMap(): FloatIntMap = EmptyFloatIntMap
+
+/**
+ * Returns a new [MutableFloatIntMap].
+ */
+public fun floatIntMapOf(): FloatIntMap = EmptyFloatIntMap
+
+/**
+ * Returns a new [FloatIntMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Float] key and [Int] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun floatIntMapOf(vararg pairs: Pair<Float, Int>): FloatIntMap =
+    MutableFloatIntMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutableFloatIntMap].
+ */
+public fun mutableFloatIntMapOf(): MutableFloatIntMap = MutableFloatIntMap()
+
+/**
+ * Returns a new [MutableFloatIntMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Float] key and [Int] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun mutableFloatIntMapOf(vararg pairs: Pair<Float, Int>): MutableFloatIntMap =
+    MutableFloatIntMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [FloatIntMap] is a container with a [Map]-like interface for
+ * [Float] primitive keys and [Int] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutableFloatIntMap].
+ *
+ * @see [MutableFloatIntMap]
+ * @see [ScatterMap]
+ */
+public sealed class FloatIntMap {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: FloatArray = EmptyFloatArray
+
+    @PublishedApi
+    @JvmField
+    internal var values: IntArray = EmptyIntArray
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key].
+     * @throws NoSuchElementException if [key] is not in the map
+     */
+    public operator fun get(key: Float): Int {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            throw NoSuchElementException("Cannot find value for key $key")
+        }
+        return values[index]
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: Float, defaultValue: Int): Int {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: Float, defaultValue: () -> Int): Int {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            return defaultValue()
+        }
+        return values[index]
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: Float, value: Int) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            block(k[index], v[index])
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: Float) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            block(k[index])
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: Int) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            block(v[index])
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (Float, Int) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (Float, Int) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (Float, Int) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: Float): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: Float): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: Int): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [FloatIntMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is FloatIntMap) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        forEach { key, value ->
+            if (value != other[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(key)
+            s.append("=")
+            s.append(value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    @PublishedApi
+    internal fun findKeyIndex(key: Float): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutableFloatIntMap] is a container with a [MutableMap]-like interface for
+ * [Float] primitive keys and [Int] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutableFloatIntMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see MutableScatterMap
+ */
+public class MutableFloatIntMap(
+    initialCapacity: Int = DefaultScatterCapacity
+) : FloatIntMap() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = FloatArray(newCapacity)
+        values = IntArray(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: Float, defaultValue: () -> Int): Int {
+        val index = findKeyIndex(key)
+        return if (index < 0) {
+            val defValue = defaultValue()
+            put(key, defValue)
+            defValue
+        } else {
+            values[index]
+        }
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: Float, value: Int) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations. Return the previous value associated with the [key],
+     * or `null` if the key was not present in the map.
+     */
+    public fun put(key: Float, value: Int) {
+        set(key, value)
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Float] key and [Int] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<Pair<Float, Int>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: FloatIntMap) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that [pair] allocated and both the [Float] key and [Int] value are
+     * boxed. Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<Float, Int>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Float] key and [Int] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<Pair<Float, Int>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: FloatIntMap): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public fun remove(key: Float) {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            removeValueAt(index)
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: Float, value: Int): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (Float, Int) -> Boolean) {
+        forEachIndexed { index ->
+            if (predicate(keys[index], values[index])) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: Float) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: FloatArray) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: FloatSet) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: FloatList) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int) {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: Float): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableFloatIntMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
index cf0c648..70b5787 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
@@ -22,6 +22,14 @@
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
 /**
  * [FloatList] is a [List]-like collection for [Float] values. It allows retrieving
  * the elements without boxing. [FloatList] is always backed by a [MutableFloatList],
@@ -835,10 +843,6 @@
     }
 }
 
-// Empty array used when nothing is allocated
-@Suppress("PrivatePropertyName")
-private val EmptyFloatArray = FloatArray(0)
-
 private val EmptyFloatList: FloatList = MutableFloatList(0)
 
 /**
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatLongMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatLongMap.kt
new file mode 100644
index 0000000..ac71a3e
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatLongMap.kt
@@ -0,0 +1,837 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyFloatLongMap = MutableFloatLongMap(0)
+
+/**
+ * Returns an empty, read-only [FloatLongMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun emptyFloatLongMap(): FloatLongMap = EmptyFloatLongMap
+
+/**
+ * Returns a new [MutableFloatLongMap].
+ */
+public fun floatLongMapOf(): FloatLongMap = EmptyFloatLongMap
+
+/**
+ * Returns a new [FloatLongMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Float] key and [Long] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun floatLongMapOf(vararg pairs: Pair<Float, Long>): FloatLongMap =
+    MutableFloatLongMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutableFloatLongMap].
+ */
+public fun mutableFloatLongMapOf(): MutableFloatLongMap = MutableFloatLongMap()
+
+/**
+ * Returns a new [MutableFloatLongMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Float] key and [Long] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun mutableFloatLongMapOf(vararg pairs: Pair<Float, Long>): MutableFloatLongMap =
+    MutableFloatLongMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [FloatLongMap] is a container with a [Map]-like interface for
+ * [Float] primitive keys and [Long] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutableFloatLongMap].
+ *
+ * @see [MutableFloatLongMap]
+ * @see [ScatterMap]
+ */
+public sealed class FloatLongMap {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: FloatArray = EmptyFloatArray
+
+    @PublishedApi
+    @JvmField
+    internal var values: LongArray = EmptyLongArray
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key].
+     * @throws NoSuchElementException if [key] is not in the map
+     */
+    public operator fun get(key: Float): Long {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            throw NoSuchElementException("Cannot find value for key $key")
+        }
+        return values[index]
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: Float, defaultValue: Long): Long {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: Float, defaultValue: () -> Long): Long {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            return defaultValue()
+        }
+        return values[index]
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: Float, value: Long) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            block(k[index], v[index])
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: Float) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            block(k[index])
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: Long) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            block(v[index])
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (Float, Long) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (Float, Long) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (Float, Long) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: Float): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: Float): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: Long): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [FloatLongMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is FloatLongMap) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        forEach { key, value ->
+            if (value != other[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(key)
+            s.append("=")
+            s.append(value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    @PublishedApi
+    internal fun findKeyIndex(key: Float): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutableFloatLongMap] is a container with a [MutableMap]-like interface for
+ * [Float] primitive keys and [Long] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutableFloatLongMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see MutableScatterMap
+ */
+public class MutableFloatLongMap(
+    initialCapacity: Int = DefaultScatterCapacity
+) : FloatLongMap() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = FloatArray(newCapacity)
+        values = LongArray(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: Float, defaultValue: () -> Long): Long {
+        val index = findKeyIndex(key)
+        return if (index < 0) {
+            val defValue = defaultValue()
+            put(key, defValue)
+            defValue
+        } else {
+            values[index]
+        }
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: Float, value: Long) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations. Return the previous value associated with the [key],
+     * or `null` if the key was not present in the map.
+     */
+    public fun put(key: Float, value: Long) {
+        set(key, value)
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Float] key and [Long] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<Pair<Float, Long>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: FloatLongMap) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that [pair] allocated and both the [Float] key and [Long] value are
+     * boxed. Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<Float, Long>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Float] key and [Long] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<Pair<Float, Long>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: FloatLongMap): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public fun remove(key: Float) {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            removeValueAt(index)
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: Float, value: Long): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (Float, Long) -> Boolean) {
+        forEachIndexed { index ->
+            if (predicate(keys[index], values[index])) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: Float) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: FloatArray) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: FloatSet) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: FloatList) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int) {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: Float): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableFloatLongMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatObjectMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatObjectMap.kt
new file mode 100644
index 0000000..292347f
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatObjectMap.kt
@@ -0,0 +1,847 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import androidx.collection.internal.EMPTY_OBJECTS
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyFloatObjectMap = MutableFloatObjectMap<Nothing>(0)
+
+/**
+ * Returns an empty, read-only [FloatObjectMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <V> emptyFloatObjectMap(): FloatObjectMap<V> = EmptyFloatObjectMap as FloatObjectMap<V>
+
+/**
+ * Returns an empty, read-only [FloatObjectMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <V> floatObjectMapOf(): FloatObjectMap<V> = EmptyFloatObjectMap as FloatObjectMap<V>
+
+/**
+ * Returns a new [FloatObjectMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * the [Float] key is boxed. Use [set] for each entry instead when it is
+ * important to reduce allocations.
+ */
+public fun <V> floatObjectMapOf(vararg pairs: Pair<Float, V>): FloatObjectMap<V> =
+    MutableFloatObjectMap<V>(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutableFloatObjectMap].
+ */
+public fun <V> mutableFloatObjectMapOf(): MutableFloatObjectMap<V> = MutableFloatObjectMap()
+
+/**
+ * Returns a new [MutableFloatObjectMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * the [Float] key is boxed. Use [set] for each entry instead when it is
+ * important to reduce allocations.
+ */
+public fun <V> mutableFloatObjectMapOf(vararg pairs: Pair<Float, V>): MutableFloatObjectMap<V> =
+    MutableFloatObjectMap<V>(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [FloatObjectMap] is a container with a [Map]-like interface for keys with
+ * [Float] primitives and reference type values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutableFloatObjectMap].
+ *
+ * @see [MutableFloatObjectMap]
+ */
+public sealed class FloatObjectMap<V> {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: FloatArray = EmptyFloatArray
+
+    @PublishedApi
+    @JvmField
+    internal var values: Array<Any?> = EMPTY_OBJECTS
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key], or `null` if such
+     * a key is not present in the map.
+     */
+    public operator fun get(key: Float): V? {
+        val index = findKeyIndex(key)
+        @Suppress("UNCHECKED_CAST")
+        return if (index >= 0) values[index] as V? else null
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: Float, defaultValue: V): V {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            @Suppress("UNCHECKED_CAST")
+            return values[index] as V
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: Float, defaultValue: () -> V): V {
+        return get(key) ?: defaultValue()
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: Float, value: V) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            block(k[index], v[index] as V)
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: Float) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            block(k[index])
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: V) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            block(v[index] as V)
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (Float, V) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (Float, V) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (Float, V) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: Float): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: Float): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: V): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [FloatObjectMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is FloatObjectMap<*>) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        forEach { key, value ->
+            if (value == null) {
+                if (other[key] != null || !other.containsKey(key)) {
+                    return false
+                }
+            } else if (value != other[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(key)
+            s.append("=")
+            s.append(if (value === this) "(this)" else value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    internal inline fun findKeyIndex(key: Float): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutableFloatObjectMap] is a container with a [MutableMap]-like interface for keys with
+ * [Float] primitives and reference type values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutableFloatObjectMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see ScatterMap
+ */
+public class MutableFloatObjectMap<V>(
+    initialCapacity: Int = DefaultScatterCapacity
+) : FloatObjectMap<V>() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = FloatArray(newCapacity)
+        values = arrayOfNulls(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: Float, defaultValue: () -> V): V {
+        return get(key) ?: defaultValue().also { set(key, it) }
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: Float, value: V) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations. Return the previous value associated with the [key],
+     * or `null` if the key was not present in the map.
+     */
+    public fun put(key: Float, value: V): V? {
+        val index = findAbsoluteInsertIndex(key)
+        val oldValue = values[index]
+        keys[index] = key
+        values[index] = value
+
+        @Suppress("UNCHECKED_CAST")
+        return oldValue as V?
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * the [Float] key is boxed. Use [set] for each entry instead when it is
+     * important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<out Pair<Float, V>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: FloatObjectMap<V>) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that the [Pair] is allocated and the [Float] key is boxed.
+     * Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<Float, V>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * the [Float] key is boxed. Use [set] for each entry instead when it is
+     * important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<out Pair<Float, V>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: FloatObjectMap<V>): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map. If the
+     * [key] was present in the map, this function returns the value that was
+     * present before removal.
+     */
+    public fun remove(key: Float): V? {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return removeValueAt(index)
+        }
+        return null
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: Float, value: V): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (Float, V) -> Boolean) {
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            if (predicate(keys[index], values[index] as V)) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: Float) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: FloatArray) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: FloatSet) {
+        keys.forEach { key ->
+            minusAssign(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: FloatList) {
+        keys.forEach { key ->
+            minusAssign(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int): V? {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+        val oldValue = values[index]
+        values[index] = null
+
+        @Suppress("UNCHECKED_CAST")
+        return oldValue as V?
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        values.fill(null, 0, _capacity)
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: Float): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableFloatObjectMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt
index 278902c4..f08a176 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt
@@ -29,13 +29,21 @@
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 
-// This is a copy of ScatterSet, but with Float elements
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// This is a copy of ScatterSet, but with primitive elements
 
 // Default empty set to avoid allocations
 private val EmptyFloatSet = MutableFloatSet(0)
 
 // An empty array of floats
-private val EmptyFloatArray = FloatArray(0)
+internal val EmptyFloatArray = FloatArray(0)
 
 /**
  * Returns an empty, read-only [FloatSet].
@@ -770,7 +778,7 @@
  * Returns the hash code of [k]. This follows the [HashSet] default behavior on Android
  * of returning [Object.hashcode()] with the higher bits of hash spread to the lower bits.
  */
-private inline fun hash(k: Float): Int {
+internal inline fun hash(k: Float): Int {
     val hash = k.hashCode()
     return hash xor (hash ushr 16)
 }
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntFloatMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntFloatMap.kt
new file mode 100644
index 0000000..b9151ff7
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntFloatMap.kt
@@ -0,0 +1,837 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyIntFloatMap = MutableIntFloatMap(0)
+
+/**
+ * Returns an empty, read-only [IntFloatMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun emptyIntFloatMap(): IntFloatMap = EmptyIntFloatMap
+
+/**
+ * Returns a new [MutableIntFloatMap].
+ */
+public fun intFloatMapOf(): IntFloatMap = EmptyIntFloatMap
+
+/**
+ * Returns a new [IntFloatMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Int] key and [Float] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun intFloatMapOf(vararg pairs: Pair<Int, Float>): IntFloatMap =
+    MutableIntFloatMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutableIntFloatMap].
+ */
+public fun mutableIntFloatMapOf(): MutableIntFloatMap = MutableIntFloatMap()
+
+/**
+ * Returns a new [MutableIntFloatMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Int] key and [Float] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun mutableIntFloatMapOf(vararg pairs: Pair<Int, Float>): MutableIntFloatMap =
+    MutableIntFloatMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [IntFloatMap] is a container with a [Map]-like interface for
+ * [Int] primitive keys and [Float] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutableIntFloatMap].
+ *
+ * @see [MutableIntFloatMap]
+ * @see [ScatterMap]
+ */
+public sealed class IntFloatMap {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: IntArray = EmptyIntArray
+
+    @PublishedApi
+    @JvmField
+    internal var values: FloatArray = EmptyFloatArray
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key].
+     * @throws NoSuchElementException if [key] is not in the map
+     */
+    public operator fun get(key: Int): Float {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            throw NoSuchElementException("Cannot find value for key $key")
+        }
+        return values[index]
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: Int, defaultValue: Float): Float {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: Int, defaultValue: () -> Float): Float {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            return defaultValue()
+        }
+        return values[index]
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: Int, value: Float) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            block(k[index], v[index])
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: Int) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            block(k[index])
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: Float) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            block(v[index])
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (Int, Float) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (Int, Float) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (Int, Float) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: Int): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: Int): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: Float): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [IntFloatMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is IntFloatMap) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        forEach { key, value ->
+            if (value != other[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(key)
+            s.append("=")
+            s.append(value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    @PublishedApi
+    internal fun findKeyIndex(key: Int): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutableIntFloatMap] is a container with a [MutableMap]-like interface for
+ * [Int] primitive keys and [Float] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutableIntFloatMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see MutableScatterMap
+ */
+public class MutableIntFloatMap(
+    initialCapacity: Int = DefaultScatterCapacity
+) : IntFloatMap() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = IntArray(newCapacity)
+        values = FloatArray(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: Int, defaultValue: () -> Float): Float {
+        val index = findKeyIndex(key)
+        return if (index < 0) {
+            val defValue = defaultValue()
+            put(key, defValue)
+            defValue
+        } else {
+            values[index]
+        }
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: Int, value: Float) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations. Return the previous value associated with the [key],
+     * or `null` if the key was not present in the map.
+     */
+    public fun put(key: Int, value: Float) {
+        set(key, value)
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Int] key and [Float] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<Pair<Int, Float>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: IntFloatMap) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that [pair] allocated and both the [Int] key and [Float] value are
+     * boxed. Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<Int, Float>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Int] key and [Float] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<Pair<Int, Float>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: IntFloatMap): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public fun remove(key: Int) {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            removeValueAt(index)
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: Int, value: Float): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (Int, Float) -> Boolean) {
+        forEachIndexed { index ->
+            if (predicate(keys[index], values[index])) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: Int) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: IntArray) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: IntSet) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: IntList) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int) {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: Int): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableIntFloatMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntMap.kt
new file mode 100644
index 0000000..5d288b2
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntMap.kt
@@ -0,0 +1,837 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyIntIntMap = MutableIntIntMap(0)
+
+/**
+ * Returns an empty, read-only [IntIntMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun emptyIntIntMap(): IntIntMap = EmptyIntIntMap
+
+/**
+ * Returns a new [MutableIntIntMap].
+ */
+public fun intIntMapOf(): IntIntMap = EmptyIntIntMap
+
+/**
+ * Returns a new [IntIntMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Int] key and [Int] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun intIntMapOf(vararg pairs: Pair<Int, Int>): IntIntMap =
+    MutableIntIntMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutableIntIntMap].
+ */
+public fun mutableIntIntMapOf(): MutableIntIntMap = MutableIntIntMap()
+
+/**
+ * Returns a new [MutableIntIntMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Int] key and [Int] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun mutableIntIntMapOf(vararg pairs: Pair<Int, Int>): MutableIntIntMap =
+    MutableIntIntMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [IntIntMap] is a container with a [Map]-like interface for
+ * [Int] primitive keys and [Int] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutableIntIntMap].
+ *
+ * @see [MutableIntIntMap]
+ * @see [ScatterMap]
+ */
+public sealed class IntIntMap {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: IntArray = EmptyIntArray
+
+    @PublishedApi
+    @JvmField
+    internal var values: IntArray = EmptyIntArray
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key].
+     * @throws NoSuchElementException if [key] is not in the map
+     */
+    public operator fun get(key: Int): Int {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            throw NoSuchElementException("Cannot find value for key $key")
+        }
+        return values[index]
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: Int, defaultValue: Int): Int {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: Int, defaultValue: () -> Int): Int {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            return defaultValue()
+        }
+        return values[index]
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: Int, value: Int) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            block(k[index], v[index])
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: Int) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            block(k[index])
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: Int) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            block(v[index])
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (Int, Int) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (Int, Int) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (Int, Int) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: Int): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: Int): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: Int): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [IntIntMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is IntIntMap) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        forEach { key, value ->
+            if (value != other[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(key)
+            s.append("=")
+            s.append(value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    @PublishedApi
+    internal fun findKeyIndex(key: Int): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutableIntIntMap] is a container with a [MutableMap]-like interface for
+ * [Int] primitive keys and [Int] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutableIntIntMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see MutableScatterMap
+ */
+public class MutableIntIntMap(
+    initialCapacity: Int = DefaultScatterCapacity
+) : IntIntMap() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = IntArray(newCapacity)
+        values = IntArray(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: Int, defaultValue: () -> Int): Int {
+        val index = findKeyIndex(key)
+        return if (index < 0) {
+            val defValue = defaultValue()
+            put(key, defValue)
+            defValue
+        } else {
+            values[index]
+        }
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: Int, value: Int) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations. Return the previous value associated with the [key],
+     * or `null` if the key was not present in the map.
+     */
+    public fun put(key: Int, value: Int) {
+        set(key, value)
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Int] key and [Int] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<Pair<Int, Int>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: IntIntMap) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that [pair] allocated and both the [Int] key and [Int] value are
+     * boxed. Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<Int, Int>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Int] key and [Int] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<Pair<Int, Int>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: IntIntMap): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public fun remove(key: Int) {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            removeValueAt(index)
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: Int, value: Int): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (Int, Int) -> Boolean) {
+        forEachIndexed { index ->
+            if (predicate(keys[index], values[index])) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: Int) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: IntArray) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: IntSet) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: IntList) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int) {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: Int): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableIntIntMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
index 8c6122db..4260def 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
@@ -22,6 +22,14 @@
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
 /**
  * [IntList] is a [List]-like collection for [Int] values. It allows retrieving
  * the elements without boxing. [IntList] is always backed by a [MutableIntList],
@@ -835,10 +843,6 @@
     }
 }
 
-// Empty array used when nothing is allocated
-@Suppress("PrivatePropertyName")
-private val EmptyIntArray = IntArray(0)
-
 private val EmptyIntList: IntList = MutableIntList(0)
 
 /**
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntLongMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntLongMap.kt
new file mode 100644
index 0000000..ce4455a
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntLongMap.kt
@@ -0,0 +1,837 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyIntLongMap = MutableIntLongMap(0)
+
+/**
+ * Returns an empty, read-only [IntLongMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun emptyIntLongMap(): IntLongMap = EmptyIntLongMap
+
+/**
+ * Returns a new [MutableIntLongMap].
+ */
+public fun intLongMapOf(): IntLongMap = EmptyIntLongMap
+
+/**
+ * Returns a new [IntLongMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Int] key and [Long] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun intLongMapOf(vararg pairs: Pair<Int, Long>): IntLongMap =
+    MutableIntLongMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutableIntLongMap].
+ */
+public fun mutableIntLongMapOf(): MutableIntLongMap = MutableIntLongMap()
+
+/**
+ * Returns a new [MutableIntLongMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Int] key and [Long] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun mutableIntLongMapOf(vararg pairs: Pair<Int, Long>): MutableIntLongMap =
+    MutableIntLongMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [IntLongMap] is a container with a [Map]-like interface for
+ * [Int] primitive keys and [Long] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutableIntLongMap].
+ *
+ * @see [MutableIntLongMap]
+ * @see [ScatterMap]
+ */
+public sealed class IntLongMap {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: IntArray = EmptyIntArray
+
+    @PublishedApi
+    @JvmField
+    internal var values: LongArray = EmptyLongArray
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key].
+     * @throws NoSuchElementException if [key] is not in the map
+     */
+    public operator fun get(key: Int): Long {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            throw NoSuchElementException("Cannot find value for key $key")
+        }
+        return values[index]
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: Int, defaultValue: Long): Long {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: Int, defaultValue: () -> Long): Long {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            return defaultValue()
+        }
+        return values[index]
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: Int, value: Long) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            block(k[index], v[index])
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: Int) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            block(k[index])
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: Long) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            block(v[index])
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (Int, Long) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (Int, Long) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (Int, Long) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: Int): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: Int): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: Long): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [IntLongMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is IntLongMap) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        forEach { key, value ->
+            if (value != other[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(key)
+            s.append("=")
+            s.append(value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    @PublishedApi
+    internal fun findKeyIndex(key: Int): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutableIntLongMap] is a container with a [MutableMap]-like interface for
+ * [Int] primitive keys and [Long] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutableIntLongMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see MutableScatterMap
+ */
+public class MutableIntLongMap(
+    initialCapacity: Int = DefaultScatterCapacity
+) : IntLongMap() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = IntArray(newCapacity)
+        values = LongArray(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: Int, defaultValue: () -> Long): Long {
+        val index = findKeyIndex(key)
+        return if (index < 0) {
+            val defValue = defaultValue()
+            put(key, defValue)
+            defValue
+        } else {
+            values[index]
+        }
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: Int, value: Long) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations. Return the previous value associated with the [key],
+     * or `null` if the key was not present in the map.
+     */
+    public fun put(key: Int, value: Long) {
+        set(key, value)
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Int] key and [Long] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<Pair<Int, Long>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: IntLongMap) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that [pair] allocated and both the [Int] key and [Long] value are
+     * boxed. Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<Int, Long>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Int] key and [Long] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<Pair<Int, Long>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: IntLongMap): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public fun remove(key: Int) {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            removeValueAt(index)
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: Int, value: Long): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (Int, Long) -> Boolean) {
+        forEachIndexed { index ->
+            if (predicate(keys[index], values[index])) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: Int) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: IntArray) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: IntSet) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: IntList) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int) {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: Int): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableIntLongMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntObjectMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntObjectMap.kt
new file mode 100644
index 0000000..1626d89
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntObjectMap.kt
@@ -0,0 +1,847 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import androidx.collection.internal.EMPTY_OBJECTS
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyIntObjectMap = MutableIntObjectMap<Nothing>(0)
+
+/**
+ * Returns an empty, read-only [IntObjectMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <V> emptyIntObjectMap(): IntObjectMap<V> = EmptyIntObjectMap as IntObjectMap<V>
+
+/**
+ * Returns an empty, read-only [IntObjectMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <V> intObjectMapOf(): IntObjectMap<V> = EmptyIntObjectMap as IntObjectMap<V>
+
+/**
+ * Returns a new [IntObjectMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * the [Int] key is boxed. Use [set] for each entry instead when it is
+ * important to reduce allocations.
+ */
+public fun <V> intObjectMapOf(vararg pairs: Pair<Int, V>): IntObjectMap<V> =
+    MutableIntObjectMap<V>(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutableIntObjectMap].
+ */
+public fun <V> mutableIntObjectMapOf(): MutableIntObjectMap<V> = MutableIntObjectMap()
+
+/**
+ * Returns a new [MutableIntObjectMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * the [Int] key is boxed. Use [set] for each entry instead when it is
+ * important to reduce allocations.
+ */
+public fun <V> mutableIntObjectMapOf(vararg pairs: Pair<Int, V>): MutableIntObjectMap<V> =
+    MutableIntObjectMap<V>(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [IntObjectMap] is a container with a [Map]-like interface for keys with
+ * [Int] primitives and reference type values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutableIntObjectMap].
+ *
+ * @see [MutableIntObjectMap]
+ */
+public sealed class IntObjectMap<V> {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: IntArray = EmptyIntArray
+
+    @PublishedApi
+    @JvmField
+    internal var values: Array<Any?> = EMPTY_OBJECTS
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key], or `null` if such
+     * a key is not present in the map.
+     */
+    public operator fun get(key: Int): V? {
+        val index = findKeyIndex(key)
+        @Suppress("UNCHECKED_CAST")
+        return if (index >= 0) values[index] as V? else null
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: Int, defaultValue: V): V {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            @Suppress("UNCHECKED_CAST")
+            return values[index] as V
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: Int, defaultValue: () -> V): V {
+        return get(key) ?: defaultValue()
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: Int, value: V) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            block(k[index], v[index] as V)
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: Int) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            block(k[index])
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: V) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            block(v[index] as V)
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (Int, V) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (Int, V) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (Int, V) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: Int): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: Int): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: V): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [IntObjectMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is IntObjectMap<*>) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        forEach { key, value ->
+            if (value == null) {
+                if (other[key] != null || !other.containsKey(key)) {
+                    return false
+                }
+            } else if (value != other[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(key)
+            s.append("=")
+            s.append(if (value === this) "(this)" else value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    internal inline fun findKeyIndex(key: Int): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutableIntObjectMap] is a container with a [MutableMap]-like interface for keys with
+ * [Int] primitives and reference type values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutableIntObjectMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see ScatterMap
+ */
+public class MutableIntObjectMap<V>(
+    initialCapacity: Int = DefaultScatterCapacity
+) : IntObjectMap<V>() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = IntArray(newCapacity)
+        values = arrayOfNulls(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: Int, defaultValue: () -> V): V {
+        return get(key) ?: defaultValue().also { set(key, it) }
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: Int, value: V) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations. Return the previous value associated with the [key],
+     * or `null` if the key was not present in the map.
+     */
+    public fun put(key: Int, value: V): V? {
+        val index = findAbsoluteInsertIndex(key)
+        val oldValue = values[index]
+        keys[index] = key
+        values[index] = value
+
+        @Suppress("UNCHECKED_CAST")
+        return oldValue as V?
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * the [Int] key is boxed. Use [set] for each entry instead when it is
+     * important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<out Pair<Int, V>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: IntObjectMap<V>) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that the [Pair] is allocated and the [Int] key is boxed.
+     * Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<Int, V>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * the [Int] key is boxed. Use [set] for each entry instead when it is
+     * important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<out Pair<Int, V>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: IntObjectMap<V>): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map. If the
+     * [key] was present in the map, this function returns the value that was
+     * present before removal.
+     */
+    public fun remove(key: Int): V? {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return removeValueAt(index)
+        }
+        return null
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: Int, value: V): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (Int, V) -> Boolean) {
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            if (predicate(keys[index], values[index] as V)) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: Int) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: IntArray) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: IntSet) {
+        keys.forEach { key ->
+            minusAssign(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: IntList) {
+        keys.forEach { key ->
+            minusAssign(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int): V? {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+        val oldValue = values[index]
+        values[index] = null
+
+        @Suppress("UNCHECKED_CAST")
+        return oldValue as V?
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        values.fill(null, 0, _capacity)
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: Int): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableIntObjectMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt
index 2db0f7f..282c94d 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt
@@ -29,13 +29,21 @@
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 
-// This is a copy of ScatterSet, but with Int elements
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// This is a copy of ScatterSet, but with primitive elements
 
 // Default empty set to avoid allocations
 private val EmptyIntSet = MutableIntSet(0)
 
 // An empty array of ints
-private val EmptyIntArray = IntArray(0)
+internal val EmptyIntArray = IntArray(0)
 
 /**
  * Returns an empty, read-only [IntSet].
@@ -770,7 +778,7 @@
  * Returns the hash code of [k]. This follows the [HashSet] default behavior on Android
  * of returning [Object.hashcode()] with the higher bits of hash spread to the lower bits.
  */
-private inline fun hash(k: Int): Int {
+internal inline fun hash(k: Int): Int {
     val hash = k.hashCode()
     return hash xor (hash ushr 16)
 }
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongFloatMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongFloatMap.kt
new file mode 100644
index 0000000..797b7fb
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongFloatMap.kt
@@ -0,0 +1,837 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyLongFloatMap = MutableLongFloatMap(0)
+
+/**
+ * Returns an empty, read-only [LongFloatMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun emptyLongFloatMap(): LongFloatMap = EmptyLongFloatMap
+
+/**
+ * Returns a new [MutableLongFloatMap].
+ */
+public fun longFloatMapOf(): LongFloatMap = EmptyLongFloatMap
+
+/**
+ * Returns a new [LongFloatMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Long] key and [Float] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun longFloatMapOf(vararg pairs: Pair<Long, Float>): LongFloatMap =
+    MutableLongFloatMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutableLongFloatMap].
+ */
+public fun mutableLongFloatMapOf(): MutableLongFloatMap = MutableLongFloatMap()
+
+/**
+ * Returns a new [MutableLongFloatMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Long] key and [Float] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun mutableLongFloatMapOf(vararg pairs: Pair<Long, Float>): MutableLongFloatMap =
+    MutableLongFloatMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [LongFloatMap] is a container with a [Map]-like interface for
+ * [Long] primitive keys and [Float] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutableLongFloatMap].
+ *
+ * @see [MutableLongFloatMap]
+ * @see [ScatterMap]
+ */
+public sealed class LongFloatMap {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: LongArray = EmptyLongArray
+
+    @PublishedApi
+    @JvmField
+    internal var values: FloatArray = EmptyFloatArray
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key].
+     * @throws NoSuchElementException if [key] is not in the map
+     */
+    public operator fun get(key: Long): Float {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            throw NoSuchElementException("Cannot find value for key $key")
+        }
+        return values[index]
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: Long, defaultValue: Float): Float {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: Long, defaultValue: () -> Float): Float {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            return defaultValue()
+        }
+        return values[index]
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: Long, value: Float) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            block(k[index], v[index])
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: Long) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            block(k[index])
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: Float) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            block(v[index])
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (Long, Float) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (Long, Float) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (Long, Float) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: Long): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: Long): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: Float): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [LongFloatMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is LongFloatMap) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        forEach { key, value ->
+            if (value != other[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(key)
+            s.append("=")
+            s.append(value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    @PublishedApi
+    internal fun findKeyIndex(key: Long): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutableLongFloatMap] is a container with a [MutableMap]-like interface for
+ * [Long] primitive keys and [Float] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutableLongFloatMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see MutableScatterMap
+ */
+public class MutableLongFloatMap(
+    initialCapacity: Int = DefaultScatterCapacity
+) : LongFloatMap() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = LongArray(newCapacity)
+        values = FloatArray(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: Long, defaultValue: () -> Float): Float {
+        val index = findKeyIndex(key)
+        return if (index < 0) {
+            val defValue = defaultValue()
+            put(key, defValue)
+            defValue
+        } else {
+            values[index]
+        }
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: Long, value: Float) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations. Return the previous value associated with the [key],
+     * or `null` if the key was not present in the map.
+     */
+    public fun put(key: Long, value: Float) {
+        set(key, value)
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Long] key and [Float] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<Pair<Long, Float>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: LongFloatMap) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that [pair] allocated and both the [Long] key and [Float] value are
+     * boxed. Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<Long, Float>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Long] key and [Float] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<Pair<Long, Float>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: LongFloatMap): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public fun remove(key: Long) {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            removeValueAt(index)
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: Long, value: Float): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (Long, Float) -> Boolean) {
+        forEachIndexed { index ->
+            if (predicate(keys[index], values[index])) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: Long) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: LongArray) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: LongSet) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: LongList) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int) {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: Long): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableLongFloatMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongIntMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongIntMap.kt
new file mode 100644
index 0000000..c9c5ef6
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongIntMap.kt
@@ -0,0 +1,837 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyLongIntMap = MutableLongIntMap(0)
+
+/**
+ * Returns an empty, read-only [LongIntMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun emptyLongIntMap(): LongIntMap = EmptyLongIntMap
+
+/**
+ * Returns a new [MutableLongIntMap].
+ */
+public fun longIntMapOf(): LongIntMap = EmptyLongIntMap
+
+/**
+ * Returns a new [LongIntMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Long] key and [Int] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun longIntMapOf(vararg pairs: Pair<Long, Int>): LongIntMap =
+    MutableLongIntMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutableLongIntMap].
+ */
+public fun mutableLongIntMapOf(): MutableLongIntMap = MutableLongIntMap()
+
+/**
+ * Returns a new [MutableLongIntMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Long] key and [Int] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun mutableLongIntMapOf(vararg pairs: Pair<Long, Int>): MutableLongIntMap =
+    MutableLongIntMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [LongIntMap] is a container with a [Map]-like interface for
+ * [Long] primitive keys and [Int] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutableLongIntMap].
+ *
+ * @see [MutableLongIntMap]
+ * @see [ScatterMap]
+ */
+public sealed class LongIntMap {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: LongArray = EmptyLongArray
+
+    @PublishedApi
+    @JvmField
+    internal var values: IntArray = EmptyIntArray
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key].
+     * @throws NoSuchElementException if [key] is not in the map
+     */
+    public operator fun get(key: Long): Int {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            throw NoSuchElementException("Cannot find value for key $key")
+        }
+        return values[index]
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: Long, defaultValue: Int): Int {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: Long, defaultValue: () -> Int): Int {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            return defaultValue()
+        }
+        return values[index]
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: Long, value: Int) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            block(k[index], v[index])
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: Long) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            block(k[index])
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: Int) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            block(v[index])
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (Long, Int) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (Long, Int) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (Long, Int) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: Long): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: Long): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: Int): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [LongIntMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is LongIntMap) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        forEach { key, value ->
+            if (value != other[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(key)
+            s.append("=")
+            s.append(value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    @PublishedApi
+    internal fun findKeyIndex(key: Long): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutableLongIntMap] is a container with a [MutableMap]-like interface for
+ * [Long] primitive keys and [Int] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutableLongIntMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see MutableScatterMap
+ */
+public class MutableLongIntMap(
+    initialCapacity: Int = DefaultScatterCapacity
+) : LongIntMap() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = LongArray(newCapacity)
+        values = IntArray(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: Long, defaultValue: () -> Int): Int {
+        val index = findKeyIndex(key)
+        return if (index < 0) {
+            val defValue = defaultValue()
+            put(key, defValue)
+            defValue
+        } else {
+            values[index]
+        }
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: Long, value: Int) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations. Return the previous value associated with the [key],
+     * or `null` if the key was not present in the map.
+     */
+    public fun put(key: Long, value: Int) {
+        set(key, value)
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Long] key and [Int] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<Pair<Long, Int>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: LongIntMap) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that [pair] allocated and both the [Long] key and [Int] value are
+     * boxed. Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<Long, Int>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Long] key and [Int] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<Pair<Long, Int>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: LongIntMap): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public fun remove(key: Long) {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            removeValueAt(index)
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: Long, value: Int): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (Long, Int) -> Boolean) {
+        forEachIndexed { index ->
+            if (predicate(keys[index], values[index])) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: Long) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: LongArray) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: LongSet) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: LongList) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int) {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: Long): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableLongIntMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
index 94dfd82..4f40b5b 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
@@ -22,6 +22,14 @@
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
 /**
  * [LongList] is a [List]-like collection for [Long] values. It allows retrieving
  * the elements without boxing. [LongList] is always backed by a [MutableLongList],
@@ -835,10 +843,6 @@
     }
 }
 
-// Empty array used when nothing is allocated
-@Suppress("PrivatePropertyName")
-private val EmptyLongArray = LongArray(0)
-
 private val EmptyLongList: LongList = MutableLongList(0)
 
 /**
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongMap.kt
new file mode 100644
index 0000000..e61a7a0
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongMap.kt
@@ -0,0 +1,837 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyLongLongMap = MutableLongLongMap(0)
+
+/**
+ * Returns an empty, read-only [LongLongMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun emptyLongLongMap(): LongLongMap = EmptyLongLongMap
+
+/**
+ * Returns a new [MutableLongLongMap].
+ */
+public fun longLongMapOf(): LongLongMap = EmptyLongLongMap
+
+/**
+ * Returns a new [LongLongMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Long] key and [Long] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun longLongMapOf(vararg pairs: Pair<Long, Long>): LongLongMap =
+    MutableLongLongMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutableLongLongMap].
+ */
+public fun mutableLongLongMapOf(): MutableLongLongMap = MutableLongLongMap()
+
+/**
+ * Returns a new [MutableLongLongMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [Long] key and [Long] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun mutableLongLongMapOf(vararg pairs: Pair<Long, Long>): MutableLongLongMap =
+    MutableLongLongMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [LongLongMap] is a container with a [Map]-like interface for
+ * [Long] primitive keys and [Long] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutableLongLongMap].
+ *
+ * @see [MutableLongLongMap]
+ * @see [ScatterMap]
+ */
+public sealed class LongLongMap {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: LongArray = EmptyLongArray
+
+    @PublishedApi
+    @JvmField
+    internal var values: LongArray = EmptyLongArray
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key].
+     * @throws NoSuchElementException if [key] is not in the map
+     */
+    public operator fun get(key: Long): Long {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            throw NoSuchElementException("Cannot find value for key $key")
+        }
+        return values[index]
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: Long, defaultValue: Long): Long {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: Long, defaultValue: () -> Long): Long {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            return defaultValue()
+        }
+        return values[index]
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: Long, value: Long) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            block(k[index], v[index])
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: Long) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            block(k[index])
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: Long) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            block(v[index])
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (Long, Long) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (Long, Long) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (Long, Long) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: Long): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: Long): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: Long): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [LongLongMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is LongLongMap) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        forEach { key, value ->
+            if (value != other[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(key)
+            s.append("=")
+            s.append(value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    @PublishedApi
+    internal fun findKeyIndex(key: Long): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutableLongLongMap] is a container with a [MutableMap]-like interface for
+ * [Long] primitive keys and [Long] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutableLongLongMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see MutableScatterMap
+ */
+public class MutableLongLongMap(
+    initialCapacity: Int = DefaultScatterCapacity
+) : LongLongMap() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = LongArray(newCapacity)
+        values = LongArray(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: Long, defaultValue: () -> Long): Long {
+        val index = findKeyIndex(key)
+        return if (index < 0) {
+            val defValue = defaultValue()
+            put(key, defValue)
+            defValue
+        } else {
+            values[index]
+        }
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: Long, value: Long) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations. Return the previous value associated with the [key],
+     * or `null` if the key was not present in the map.
+     */
+    public fun put(key: Long, value: Long) {
+        set(key, value)
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Long] key and [Long] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<Pair<Long, Long>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: LongLongMap) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that [pair] allocated and both the [Long] key and [Long] value are
+     * boxed. Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<Long, Long>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [Long] key and [Long] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<Pair<Long, Long>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: LongLongMap): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public fun remove(key: Long) {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            removeValueAt(index)
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: Long, value: Long): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (Long, Long) -> Boolean) {
+        forEachIndexed { index ->
+            if (predicate(keys[index], values[index])) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: Long) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: LongArray) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: LongSet) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: LongList) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int) {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: Long): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableLongLongMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongObjectMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongObjectMap.kt
new file mode 100644
index 0000000..f01c754
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongObjectMap.kt
@@ -0,0 +1,847 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import androidx.collection.internal.EMPTY_OBJECTS
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyLongObjectMap = MutableLongObjectMap<Nothing>(0)
+
+/**
+ * Returns an empty, read-only [LongObjectMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <V> emptyLongObjectMap(): LongObjectMap<V> = EmptyLongObjectMap as LongObjectMap<V>
+
+/**
+ * Returns an empty, read-only [LongObjectMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <V> longObjectMapOf(): LongObjectMap<V> = EmptyLongObjectMap as LongObjectMap<V>
+
+/**
+ * Returns a new [LongObjectMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * the [Long] key is boxed. Use [set] for each entry instead when it is
+ * important to reduce allocations.
+ */
+public fun <V> longObjectMapOf(vararg pairs: Pair<Long, V>): LongObjectMap<V> =
+    MutableLongObjectMap<V>(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutableLongObjectMap].
+ */
+public fun <V> mutableLongObjectMapOf(): MutableLongObjectMap<V> = MutableLongObjectMap()
+
+/**
+ * Returns a new [MutableLongObjectMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * the [Long] key is boxed. Use [set] for each entry instead when it is
+ * important to reduce allocations.
+ */
+public fun <V> mutableLongObjectMapOf(vararg pairs: Pair<Long, V>): MutableLongObjectMap<V> =
+    MutableLongObjectMap<V>(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [LongObjectMap] is a container with a [Map]-like interface for keys with
+ * [Long] primitives and reference type values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutableLongObjectMap].
+ *
+ * @see [MutableLongObjectMap]
+ */
+public sealed class LongObjectMap<V> {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: LongArray = EmptyLongArray
+
+    @PublishedApi
+    @JvmField
+    internal var values: Array<Any?> = EMPTY_OBJECTS
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key], or `null` if such
+     * a key is not present in the map.
+     */
+    public operator fun get(key: Long): V? {
+        val index = findKeyIndex(key)
+        @Suppress("UNCHECKED_CAST")
+        return if (index >= 0) values[index] as V? else null
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: Long, defaultValue: V): V {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            @Suppress("UNCHECKED_CAST")
+            return values[index] as V
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: Long, defaultValue: () -> V): V {
+        return get(key) ?: defaultValue()
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: Long, value: V) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            block(k[index], v[index] as V)
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: Long) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            block(k[index])
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: V) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            block(v[index] as V)
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (Long, V) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (Long, V) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (Long, V) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: Long): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: Long): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: V): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [LongObjectMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is LongObjectMap<*>) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        forEach { key, value ->
+            if (value == null) {
+                if (other[key] != null || !other.containsKey(key)) {
+                    return false
+                }
+            } else if (value != other[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(key)
+            s.append("=")
+            s.append(if (value === this) "(this)" else value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    internal inline fun findKeyIndex(key: Long): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutableLongObjectMap] is a container with a [MutableMap]-like interface for keys with
+ * [Long] primitives and reference type values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutableLongObjectMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see ScatterMap
+ */
+public class MutableLongObjectMap<V>(
+    initialCapacity: Int = DefaultScatterCapacity
+) : LongObjectMap<V>() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = LongArray(newCapacity)
+        values = arrayOfNulls(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: Long, defaultValue: () -> V): V {
+        return get(key) ?: defaultValue().also { set(key, it) }
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: Long, value: V) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations. Return the previous value associated with the [key],
+     * or `null` if the key was not present in the map.
+     */
+    public fun put(key: Long, value: V): V? {
+        val index = findAbsoluteInsertIndex(key)
+        val oldValue = values[index]
+        keys[index] = key
+        values[index] = value
+
+        @Suppress("UNCHECKED_CAST")
+        return oldValue as V?
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * the [Long] key is boxed. Use [set] for each entry instead when it is
+     * important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<out Pair<Long, V>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: LongObjectMap<V>) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that the [Pair] is allocated and the [Long] key is boxed.
+     * Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<Long, V>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * the [Long] key is boxed. Use [set] for each entry instead when it is
+     * important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<out Pair<Long, V>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: LongObjectMap<V>): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map. If the
+     * [key] was present in the map, this function returns the value that was
+     * present before removal.
+     */
+    public fun remove(key: Long): V? {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return removeValueAt(index)
+        }
+        return null
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: Long, value: V): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (Long, V) -> Boolean) {
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            if (predicate(keys[index], values[index] as V)) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: Long) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: LongArray) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: LongSet) {
+        keys.forEach { key ->
+            minusAssign(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: LongList) {
+        keys.forEach { key ->
+            minusAssign(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int): V? {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+        val oldValue = values[index]
+        values[index] = null
+
+        @Suppress("UNCHECKED_CAST")
+        return oldValue as V?
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        values.fill(null, 0, _capacity)
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: Long): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableLongObjectMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt
index f292716..45734c8 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt
@@ -29,13 +29,21 @@
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 
-// This is a copy of ScatterSet, but with Long elements
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// This is a copy of ScatterSet, but with primitive elements
 
 // Default empty set to avoid allocations
 private val EmptyLongSet = MutableLongSet(0)
 
 // An empty array of longs
-private val EmptyLongArray = LongArray(0)
+internal val EmptyLongArray = LongArray(0)
 
 /**
  * Returns an empty, read-only [LongSet].
@@ -770,7 +778,7 @@
  * Returns the hash code of [k]. This follows the [HashSet] default behavior on Android
  * of returning [Object.hashcode()] with the higher bits of hash spread to the lower bits.
  */
-private inline fun hash(k: Long): Int {
+internal inline fun hash(k: Long): Int {
     val hash = k.hashCode()
     return hash xor (hash ushr 16)
 }
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectFloatMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectFloatMap.kt
new file mode 100644
index 0000000..95c1a3d
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectFloatMap.kt
@@ -0,0 +1,855 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import androidx.collection.internal.EMPTY_OBJECTS
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyObjectFloatMap = MutableObjectFloatMap<Any?>(0)
+
+/**
+ * Returns an empty, read-only [ObjectFloatMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <K> emptyObjectFloatMap(): ObjectFloatMap<K> =
+    EmptyObjectFloatMap as ObjectFloatMap<K>
+
+/**
+ * Returns an empty, read-only [ObjectFloatMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <K> objectFloatMap(): ObjectFloatMap<K> =
+    EmptyObjectFloatMap as ObjectFloatMap<K>
+
+/**
+ * Returns a new [MutableObjectFloatMap].
+ */
+public fun <K> mutableObjectFloatMapOf(): MutableObjectFloatMap<K> = MutableObjectFloatMap()
+
+/**
+ * Returns a new [MutableObjectFloatMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * the [Float] value is boxed. Use [set] for each entry instead when it is
+ * important to reduce allocations.
+ */
+public fun <K> objectFloatMapOf(vararg pairs: Pair<K, Float>): ObjectFloatMap<K> =
+    MutableObjectFloatMap<K>(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutableObjectFloatMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * the [Float] value is boxed. Use [set] for each entry instead when it is
+ * important to reduce allocations.
+ */
+public fun <K> mutableObjectFloatMapOf(vararg pairs: Pair<K, Float>): MutableObjectFloatMap<K> =
+    MutableObjectFloatMap<K>(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [ObjectFloatMap] is a container with a [Map]-like interface for keys with
+ * reference types and [Float] primitives for values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutableObjectFloatMap].
+ *
+ * @see [MutableObjectFloatMap]
+ * @see ScatterMap
+ */
+public sealed class ObjectFloatMap<K> {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: Array<Any?> = EMPTY_OBJECTS
+
+    @PublishedApi
+    @JvmField
+    internal var values: FloatArray = EmptyFloatArray
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key], or `null` if such
+     * a key is not present in the map.
+     * @throws NoSuchElementException when [key] is not found
+     */
+    public operator fun get(key: K): Float {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            throw NoSuchElementException("There is no key $key in the map")
+        }
+        return values[index]
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: K, defaultValue: Float): Float {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: K, defaultValue: () -> Float): Float {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue()
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: K, value: Float) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            block(k[index] as K, v[index])
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: K) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            block(k[index] as K)
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: Float) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            block(v[index])
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (K, Float) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (K, Float) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (K, Float) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: K): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: K): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: Float): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [ObjectFloatMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is ObjectFloatMap<*>) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        @Suppress("UNCHECKED_CAST")
+        val o = other as ObjectFloatMap<Any?>
+
+        forEach { key, value ->
+            if (value != o[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(if (key === this) "(this)" else key)
+            s.append("=")
+            s.append(value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    @PublishedApi
+    internal fun findKeyIndex(key: K): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutableObjectFloatMap] is a container with a [MutableMap]-like interface for keys with
+ * reference types and [Float] primitives for values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutableObjectFloatMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see MutableScatterMap
+ */
+public class MutableObjectFloatMap<K>(
+    initialCapacity: Int = DefaultScatterCapacity
+) : ObjectFloatMap<K>() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = arrayOfNulls(newCapacity)
+        values = FloatArray(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: K, defaultValue: () -> Float): Float {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        val value = defaultValue()
+        set(key, value)
+        return value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: K, value: Float) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public fun put(key: K, value: Float) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * the [Float] value is boxed. Use [set] for each entry instead when it is
+     * important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<out Pair<K, Float>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: ObjectFloatMap<K>) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that the [Pair] is allocated and the [Float] value is boxed.
+     * Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<K, Float>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * the [Float] value is boxed. Use [set] for each entry instead when it is
+     * important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<out Pair<K, Float>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: ObjectFloatMap<K>): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public fun remove(key: K) {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            removeValueAt(index)
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: K, value: Float): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (K, Float) -> Boolean) {
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            if (predicate(keys[index] as K, values[index])) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: K) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: Array<out K>) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: Iterable<K>) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: Sequence<K>) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: ScatterSet<K>) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int) {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+        keys[index] = null
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        keys.fill(null, 0, _capacity)
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: K): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableObjectFloatMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectIntMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectIntMap.kt
new file mode 100644
index 0000000..697f1b0
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectIntMap.kt
@@ -0,0 +1,855 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import androidx.collection.internal.EMPTY_OBJECTS
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyObjectIntMap = MutableObjectIntMap<Any?>(0)
+
+/**
+ * Returns an empty, read-only [ObjectIntMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <K> emptyObjectIntMap(): ObjectIntMap<K> =
+    EmptyObjectIntMap as ObjectIntMap<K>
+
+/**
+ * Returns an empty, read-only [ObjectIntMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <K> objectIntMap(): ObjectIntMap<K> =
+    EmptyObjectIntMap as ObjectIntMap<K>
+
+/**
+ * Returns a new [MutableObjectIntMap].
+ */
+public fun <K> mutableObjectIntMapOf(): MutableObjectIntMap<K> = MutableObjectIntMap()
+
+/**
+ * Returns a new [MutableObjectIntMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * the [Int] value is boxed. Use [set] for each entry instead when it is
+ * important to reduce allocations.
+ */
+public fun <K> objectIntMapOf(vararg pairs: Pair<K, Int>): ObjectIntMap<K> =
+    MutableObjectIntMap<K>(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutableObjectIntMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * the [Int] value is boxed. Use [set] for each entry instead when it is
+ * important to reduce allocations.
+ */
+public fun <K> mutableObjectIntMapOf(vararg pairs: Pair<K, Int>): MutableObjectIntMap<K> =
+    MutableObjectIntMap<K>(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [ObjectIntMap] is a container with a [Map]-like interface for keys with
+ * reference types and [Int] primitives for values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutableObjectIntMap].
+ *
+ * @see [MutableObjectIntMap]
+ * @see ScatterMap
+ */
+public sealed class ObjectIntMap<K> {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: Array<Any?> = EMPTY_OBJECTS
+
+    @PublishedApi
+    @JvmField
+    internal var values: IntArray = EmptyIntArray
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key], or `null` if such
+     * a key is not present in the map.
+     * @throws NoSuchElementException when [key] is not found
+     */
+    public operator fun get(key: K): Int {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            throw NoSuchElementException("There is no key $key in the map")
+        }
+        return values[index]
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: K, defaultValue: Int): Int {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: K, defaultValue: () -> Int): Int {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue()
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: K, value: Int) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            block(k[index] as K, v[index])
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: K) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            block(k[index] as K)
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: Int) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            block(v[index])
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (K, Int) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (K, Int) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (K, Int) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: K): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: K): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: Int): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [ObjectIntMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is ObjectIntMap<*>) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        @Suppress("UNCHECKED_CAST")
+        val o = other as ObjectIntMap<Any?>
+
+        forEach { key, value ->
+            if (value != o[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(if (key === this) "(this)" else key)
+            s.append("=")
+            s.append(value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    @PublishedApi
+    internal fun findKeyIndex(key: K): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutableObjectIntMap] is a container with a [MutableMap]-like interface for keys with
+ * reference types and [Int] primitives for values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutableObjectIntMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see MutableScatterMap
+ */
+public class MutableObjectIntMap<K>(
+    initialCapacity: Int = DefaultScatterCapacity
+) : ObjectIntMap<K>() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = arrayOfNulls(newCapacity)
+        values = IntArray(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: K, defaultValue: () -> Int): Int {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        val value = defaultValue()
+        set(key, value)
+        return value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: K, value: Int) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public fun put(key: K, value: Int) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * the [Int] value is boxed. Use [set] for each entry instead when it is
+     * important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<out Pair<K, Int>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: ObjectIntMap<K>) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that the [Pair] is allocated and the [Int] value is boxed.
+     * Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<K, Int>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * the [Int] value is boxed. Use [set] for each entry instead when it is
+     * important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<out Pair<K, Int>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: ObjectIntMap<K>): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public fun remove(key: K) {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            removeValueAt(index)
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: K, value: Int): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (K, Int) -> Boolean) {
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            if (predicate(keys[index] as K, values[index])) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: K) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: Array<out K>) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: Iterable<K>) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: Sequence<K>) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: ScatterSet<K>) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int) {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+        keys[index] = null
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        keys.fill(null, 0, _capacity)
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: K): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableObjectIntMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectList.kt
new file mode 100644
index 0000000..105ebaf
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectList.kt
@@ -0,0 +1,1560 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:Suppress("NOTHING_TO_INLINE", "RedundantVisibilityModifier", "UNCHECKED_CAST")
+@file:OptIn(ExperimentalContracts::class)
+
+package androidx.collection
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+import kotlin.jvm.JvmField
+
+/**
+ * [ObjectList] is a [List]-like collection for reference types. It is optimized for fast
+ * access, avoiding virtual and interface method access. Methods avoid allocation whenever
+ * possible. For example [forEach] does not need allocate an [Iterator].
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the list (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. It is also not safe to mutate during reentrancy --
+ * in the middle of a [forEach], for example. However, concurrent reads are safe.
+ *
+ * **Note** [List] access is available through [asList] when developers need access to the
+ * common API.
+ *
+ * @see MutableObjectList
+ * @see FloatList
+ * @see IntList
+ * @eee LongList
+ */
+public sealed class ObjectList<E>(initialCapacity: Int) {
+    @JvmField
+    @PublishedApi
+    internal var content: Array<Any?> = if (initialCapacity == 0) {
+        EmptyArray
+    } else {
+        arrayOfNulls(initialCapacity)
+    }
+
+    @Suppress("PropertyName")
+    @JvmField
+    @PublishedApi
+    internal var _size: Int = 0
+
+    /**
+     * The number of elements in the [ObjectList].
+     */
+    @get:androidx.annotation.IntRange(from = 0)
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns the last valid index in the [ObjectList]. This can be `-1` when the list is empty.
+     */
+    @get:androidx.annotation.IntRange(from = -1)
+    public inline val lastIndex: Int get() = _size - 1
+
+    /**
+     * Returns an [IntRange] of the valid indices for this [ObjectList].
+     */
+    public inline val indices: IntRange get() = 0 until _size
+
+    /**
+     * Returns `true` if the collection has no elements in it.
+     */
+    public fun none(): Boolean {
+        return isEmpty()
+    }
+
+    /**
+     * Returns `true` if there's at least one element in the collection.
+     */
+    public fun any(): Boolean {
+        return isNotEmpty()
+    }
+
+    /**
+     * Returns `true` if any of the elements give a `true` return value for [predicate].
+     */
+    public inline fun any(predicate: (element: E) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        forEach {
+            if (predicate(it)) {
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Returns `true` if any of the elements give a `true` return value for [predicate] while
+     * iterating in the reverse order.
+     */
+    public inline fun reversedAny(predicate: (element: E) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        forEachReversed {
+            if (predicate(it)) {
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Returns `true` if the [ObjectList] contains [element] or `false` otherwise.
+     */
+    public operator fun contains(element: E): Boolean {
+        forEach {
+            if (it == element) {
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Returns `true` if the [ObjectList] contains all elements in [elements] or `false` if
+     * one or more are missing.
+     */
+    public fun containsAll(@Suppress("ArrayReturn") elements: Array<E>): Boolean {
+        for (i in elements.indices) {
+            if (!contains(elements[i])) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns `true` if the [ObjectList] contains all elements in [elements] or `false` if
+     * one or more are missing.
+     */
+    public fun containsAll(elements: List<E>): Boolean {
+        for (i in elements.indices) {
+            if (!contains(elements[i])) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns `true` if the [ObjectList] contains all elements in [elements] or `false` if
+     * one or more are missing.
+     */
+    public fun containsAll(elements: Iterable<E>): Boolean {
+        elements.forEach { element ->
+            if (!contains(element)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns `true` if the [ObjectList] contains all elements in [elements] or `false` if
+     * one or more are missing.
+     */
+    public fun containsAll(elements: ObjectList<E>): Boolean {
+        elements.forEach { element ->
+            if (!contains(element)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns the number of elements in this list.
+     */
+    public fun count(): Int = _size
+
+    /**
+     * Counts the number of elements matching [predicate].
+     * @return The number of elements in this list for which [predicate] returns true.
+     */
+    public inline fun count(predicate: (element: E) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        var count = 0
+        forEach { if (predicate(it)) count++ }
+        return count
+    }
+
+    /**
+     * Returns the first element in the [ObjectList] or throws a [NoSuchElementException] if
+     * it [isEmpty].
+     */
+    public fun first(): E {
+        if (isEmpty()) {
+            throw NoSuchElementException("ObjectList is empty.")
+        }
+        return content[0] as E
+    }
+
+    /**
+     * Returns the first element in the [ObjectList] for which [predicate] returns `true` or
+     * throws [NoSuchElementException] if nothing matches.
+     * @see indexOfFirst
+     * @see firstOrNull
+     */
+    public inline fun first(predicate: (element: E) -> Boolean): E {
+        contract { callsInPlace(predicate) }
+        forEach { element ->
+            if (predicate(element)) return element
+        }
+        throw NoSuchElementException("ObjectList contains no element matching the predicate.")
+    }
+
+    /**
+     * Returns the first element in the [ObjectList] or `null` if it [isEmpty].
+     */
+    public inline fun firstOrNull(): E? = if (isEmpty()) null else get(0)
+
+    /**
+     * Returns the first element in the [ObjectList] for which [predicate] returns `true` or
+     * `null` if nothing matches.
+     * @see indexOfFirst
+     */
+    public inline fun firstOrNull(predicate: (element: E) -> Boolean): E? {
+        contract { callsInPlace(predicate) }
+        forEach { element ->
+            if (predicate(element)) return element
+        }
+        return null
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [ObjectList] in order.
+     * @param initial The value of `acc` for the first call to [operation] or return value if
+     * there are no elements in this list.
+     * @param operation function that takes current accumulator value and an element, and
+     * calculates the next accumulator value.
+     */
+    public inline fun <R> fold(initial: R, operation: (acc: R, element: E) -> R): R {
+        contract { callsInPlace(operation) }
+        var acc = initial
+        forEach { element ->
+            acc = operation(acc, element)
+        }
+        return acc
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [ObjectList] in order.
+     */
+    public inline fun <R> foldIndexed(
+        initial: R,
+        operation: (index: Int, acc: R, element: E) -> R
+    ): R {
+        contract { callsInPlace(operation) }
+        var acc = initial
+        forEachIndexed { i, element ->
+            acc = operation(i, acc, element)
+        }
+        return acc
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [ObjectList] in reverse order.
+     * @param initial The value of `acc` for the first call to [operation] or return value if
+     * there are no elements in this list.
+     * @param operation function that takes an element and the current accumulator value, and
+     * calculates the next accumulator value.
+     */
+    public inline fun <R> foldRight(initial: R, operation: (element: E, acc: R) -> R): R {
+        contract { callsInPlace(operation) }
+        var acc = initial
+        forEachReversed { element ->
+            acc = operation(element, acc)
+        }
+        return acc
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [ObjectList] in reverse order.
+     */
+    public inline fun <R> foldRightIndexed(
+        initial: R,
+        operation: (index: Int, element: E, acc: R) -> R
+    ): R {
+        contract { callsInPlace(operation) }
+        var acc = initial
+        forEachReversedIndexed { i, element ->
+            acc = operation(i, element, acc)
+        }
+        return acc
+    }
+
+    /**
+     * Calls [block] for each element in the [ObjectList], in order.
+     * @param block will be executed for every element in the list, accepting an element from
+     * the list
+     */
+    public inline fun forEach(block: (element: E) -> Unit) {
+        contract { callsInPlace(block) }
+        val content = content
+        for (i in 0 until _size) {
+            block(content[i] as E)
+        }
+    }
+
+    /**
+     * Calls [block] for each element in the [ObjectList] along with its index, in order.
+     * @param block will be executed for every element in the list, accepting the index and
+     * the element at that index.
+     */
+    public inline fun forEachIndexed(block: (index: Int, element: E) -> Unit) {
+        contract { callsInPlace(block) }
+        val content = content
+        for (i in 0 until _size) {
+            block(i, content[i] as E)
+        }
+    }
+
+    /**
+     * Calls [block] for each element in the [ObjectList] in reverse order.
+     * @param block will be executed for every element in the list, accepting an element from
+     * the list
+     */
+    public inline fun forEachReversed(block: (element: E) -> Unit) {
+        contract { callsInPlace(block) }
+        val content = content
+        for (i in _size - 1 downTo 0) {
+            block(content[i] as E)
+        }
+    }
+
+    /**
+     * Calls [block] for each element in the [ObjectList] along with its index, in reverse
+     * order.
+     * @param block will be executed for every element in the list, accepting the index and
+     * the element at that index.
+     */
+    public inline fun forEachReversedIndexed(block: (index: Int, element: E) -> Unit) {
+        contract { callsInPlace(block) }
+        val content = content
+        for (i in _size - 1 downTo 0) {
+            block(i, content[i] as E)
+        }
+    }
+
+    /**
+     * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if
+     * the [index] is out of bounds of this collection.
+     */
+    public operator fun get(@androidx.annotation.IntRange(from = 0) index: Int): E {
+        if (index !in 0 until _size) {
+            throw IndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+        }
+        return content[index] as E
+    }
+
+    /**
+     * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if
+     * the [index] is out of bounds of this collection.
+     */
+    public fun elementAt(@androidx.annotation.IntRange(from = 0) index: Int): E {
+        if (index !in 0 until _size) {
+            throw IndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+        }
+        return content[index] as E
+    }
+
+    /**
+     * Returns the element at the given [index] or [defaultValue] if [index] is out of bounds
+     * of the collection.
+     * @param index The index of the element whose value should be returned
+     * @param defaultValue A lambda to call with [index] as a parameter to return a value at
+     * an index not in the list.
+     */
+    public inline fun elementAtOrElse(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        defaultValue: (index: Int) -> E
+    ): E {
+        if (index !in 0 until _size) {
+            return defaultValue(index)
+        }
+        return content[index] as E
+    }
+
+    /**
+     * Returns the index of [element] in the [ObjectList] or `-1` if [element] is not there.
+     */
+    public fun indexOf(element: E): Int {
+        forEachIndexed { i, item ->
+            if (element == item) {
+                return i
+            }
+        }
+        return -1
+    }
+
+    /**
+     * Returns the index if the first element in the [ObjectList] for which [predicate]
+     * returns `true` or -1 if there was no element for which predicate returned `true`.
+     */
+    public inline fun indexOfFirst(predicate: (element: E) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        forEachIndexed { i, element ->
+            if (predicate(element)) {
+                return i
+            }
+        }
+        return -1
+    }
+
+    /**
+     * Returns the index if the last element in the [ObjectList] for which [predicate]
+     * returns `true` or -1 if there was no element for which predicate returned `true`.
+     */
+    public inline fun indexOfLast(predicate: (element: E) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        forEachReversedIndexed { i, element ->
+            if (predicate(element)) {
+                return i
+            }
+        }
+        return -1
+    }
+
+    /**
+     * Returns `true` if the [ObjectList] has no elements in it or `false` otherwise.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if there are elements in the [ObjectList] or `false` if it is empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the last element in the [ObjectList] or throws a [NoSuchElementException] if
+     * it [isEmpty].
+     */
+    public fun last(): E {
+        if (isEmpty()) {
+            throw NoSuchElementException("ObjectList is empty.")
+        }
+        return content[lastIndex] as E
+    }
+
+    /**
+     * Returns the last element in the [ObjectList] for which [predicate] returns `true` or
+     * throws [NoSuchElementException] if nothing matches.
+     * @see indexOfLast
+     * @see lastOrNull
+     */
+    public inline fun last(predicate: (element: E) -> Boolean): E {
+        contract { callsInPlace(predicate) }
+        forEachReversed { element ->
+            if (predicate(element)) {
+                return element
+            }
+        }
+        throw NoSuchElementException("ObjectList contains no element matching the predicate.")
+    }
+
+    /**
+     * Returns the last element in the [ObjectList] or `null` if it [isEmpty].
+     */
+    public inline fun lastOrNull(): E? = if (isEmpty()) null else content[lastIndex] as E
+
+    /**
+     * Returns the last element in the [ObjectList] for which [predicate] returns `true` or
+     * `null` if nothing matches.
+     * @see indexOfLast
+     */
+    public inline fun lastOrNull(predicate: (element: E) -> Boolean): E? {
+        contract { callsInPlace(predicate) }
+        forEachReversed { element ->
+            if (predicate(element)) {
+                return element
+            }
+        }
+        return null
+    }
+
+    /**
+     * Returns the index of the last element in the [ObjectList] that is the same as
+     * [element] or `-1` if no elements match.
+     */
+    public fun lastIndexOf(element: E): Int {
+        forEachReversedIndexed { i, item ->
+            if (element == item) {
+                return i
+            }
+        }
+        return -1
+    }
+
+    /**
+     * Returns a [List] view into the [ObjectList]. All access to the collection will be
+     * less efficient and abides by the allocation requirements of the [List]. For example,
+     * [List.forEach] will allocate an iterator. All access will go through the more expensive
+     * interface calls. Critical performance areas should use the [ObjectList] API rather than
+     * [List] API, when possible.
+     */
+    public abstract fun asList(): List<E>
+
+    /**
+     * Returns a hash code based on the contents of the [ObjectList].
+     */
+    override fun hashCode(): Int {
+        var hashCode = 0
+        forEach { element ->
+            hashCode += 31 * element.hashCode()
+        }
+        return hashCode
+    }
+
+    /**
+     * Returns `true` if [other] is a [ObjectList] and the contents of this and [other] are the
+     * same.
+     */
+    override fun equals(other: Any?): Boolean {
+        if (other !is ObjectList<*> || other._size != _size) {
+            return false
+        }
+        val content = content
+        val otherContent = other.content
+        for (i in indices) {
+            if (content[i] != otherContent[i]) {
+                return false
+            }
+        }
+        return true
+    }
+
+    /**
+     * Returns a String representation of the list, surrounded by "[]" and each element
+     * separated by ", ".
+     */
+    override fun toString(): String {
+        if (isEmpty()) {
+            return "[]"
+        }
+        val last = lastIndex
+        return buildString {
+            append('[')
+            val content = content
+            for (i in 0 until last) {
+                append(content[i])
+                append(',')
+                append(' ')
+            }
+            append(content[last])
+            append(']')
+        }
+    }
+}
+
+/**
+ * [MutableObjectList] is a [MutableList]-like collection for reference types. It is optimized
+ * for fast access, avoiding virtual and interface method access. Methods avoid allocation
+ * whenever possible. For example [forEach] does not need allocate an [Iterator].
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the list (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. It is also not safe to mutate during reentrancy --
+ * in the middle of a [forEach], for example. However, concurrent reads are safe.
+ *
+ * **Note** [List] access is available through [asList] when developers need access to the
+ * common API.
+
+ * **Note** [MutableList] access is available through [asMutableList] when developers need
+ * access to the common API.
+ *
+ * @see ObjectList
+ * @see MutableFloatList
+ * @see MutableIntList
+ * @eee MutableLongList
+ */
+public class MutableObjectList<E>(
+    initialCapacity: Int = 16
+) : ObjectList<E>(initialCapacity) {
+    private var list: ObjectListMutableList<E>? = null
+
+    /**
+     * Returns the total number of elements that can be held before the [MutableObjectList] must
+     * grow.
+     *
+     * @see ensureCapacity
+     */
+    public inline val capacity: Int
+        get() = content.size
+
+    /**
+     * Adds [element] to the [MutableObjectList] and returns `true`.
+     */
+    public fun add(element: E): Boolean {
+        ensureCapacity(_size + 1)
+        content[_size] = element
+        _size++
+        return true
+    }
+
+    /**
+     * Adds [element] to the [MutableObjectList] at the given [index], shifting over any
+     * elements at [index] and after, if any.
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
+     */
+    public fun add(@androidx.annotation.IntRange(from = 0) index: Int, element: E) {
+        if (index !in 0.._size) {
+            throw IndexOutOfBoundsException("Index $index must be in 0..$_size")
+        }
+        ensureCapacity(_size + 1)
+        val content = content
+        if (index != _size) {
+            content.copyInto(
+                destination = content,
+                destinationOffset = index + 1,
+                startIndex = index,
+                endIndex = _size
+            )
+        }
+        content[index] = element
+        _size++
+    }
+
+    /**
+     * Adds all [elements] to the [MutableObjectList] at the given [index], shifting over any
+     * elements at [index] and after, if any.
+     * @return `true` if the [MutableObjectList] was changed or `false` if [elements] was empty
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive.
+     */
+    public fun addAll(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        @Suppress("ArrayReturn") elements: Array<E>
+    ): Boolean {
+        if (index !in 0.._size) {
+            throw IndexOutOfBoundsException("Index $index must be in 0..$_size")
+        }
+        if (elements.isEmpty()) return false
+        ensureCapacity(_size + elements.size)
+        val content = content
+        if (index != _size) {
+            content.copyInto(
+                destination = content,
+                destinationOffset = index + elements.size,
+                startIndex = index,
+                endIndex = _size
+            )
+        }
+        elements.copyInto(content, index)
+        _size += elements.size
+        return true
+    }
+
+    /**
+     * Adds all [elements] to the [MutableObjectList] at the given [index], shifting over any
+     * elements at [index] and after, if any.
+     * @return `true` if the [MutableObjectList] was changed or `false` if [elements] was empty
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive.
+     */
+    public fun addAll(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        elements: Collection<E>
+    ): Boolean {
+        if (index !in 0.._size) {
+            throw IndexOutOfBoundsException("Index $index must be in 0..$_size")
+        }
+        if (elements.isEmpty()) return false
+        ensureCapacity(_size + elements.size)
+        val content = content
+        if (index != _size) {
+            content.copyInto(
+                destination = content,
+                destinationOffset = index + elements.size,
+                startIndex = index,
+                endIndex = _size
+            )
+        }
+        elements.forEachIndexed { i, element ->
+            content[index + i] = element
+        }
+        _size += elements.size
+        return true
+    }
+
+    /**
+     * Adds all [elements] to the [MutableObjectList] at the given [index], shifting over any
+     * elements at [index] and after, if any.
+     * @return `true` if the [MutableObjectList] was changed or `false` if [elements] was empty
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
+     */
+    public fun addAll(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        elements: ObjectList<E>
+    ): Boolean {
+        if (index !in 0.._size) {
+            throw IndexOutOfBoundsException("Index $index must be in 0..$_size")
+        }
+        if (elements.isEmpty()) return false
+        ensureCapacity(_size + elements._size)
+        val content = content
+        if (index != _size) {
+            content.copyInto(
+                destination = content,
+                destinationOffset = index + elements._size,
+                startIndex = index,
+                endIndex = _size
+            )
+        }
+        elements.content.copyInto(
+            destination = content,
+            destinationOffset = index,
+            startIndex = 0,
+            endIndex = elements._size
+        )
+        _size += elements._size
+        return true
+    }
+
+    /**
+     * Adds all [elements] to the end of the [MutableObjectList] and returns `true` if the
+     * [MutableObjectList] was changed or `false` if [elements] was empty.
+     */
+    public fun addAll(elements: ObjectList<E>): Boolean {
+        val oldSize = _size
+        plusAssign(elements)
+        return oldSize != _size
+    }
+
+    /**
+     * Adds all [elements] to the end of the [MutableObjectList] and returns `true` if the
+     * [MutableObjectList] was changed or `false` if [elements] was empty.
+     */
+    public fun addAll(elements: ScatterSet<E>): Boolean {
+        val oldSize = _size
+        plusAssign(elements)
+        return oldSize != _size
+    }
+
+    /**
+     * Adds all [elements] to the end of the [MutableObjectList] and returns `true` if the
+     * [MutableObjectList] was changed or `false` if [elements] was empty.
+     */
+    public fun addAll(@Suppress("ArrayReturn") elements: Array<E>): Boolean {
+        val oldSize = _size
+        plusAssign(elements)
+        return oldSize != _size
+    }
+
+    /**
+     * Adds all [elements] to the end of the [MutableObjectList] and returns `true` if the
+     * [MutableObjectList] was changed or `false` if [elements] was empty.
+     */
+    public fun addAll(elements: List<E>): Boolean {
+        val oldSize = _size
+        plusAssign(elements)
+        return oldSize != _size
+    }
+
+    /**
+     * Adds all [elements] to the end of the [MutableObjectList] and returns `true` if the
+     * [MutableObjectList] was changed or `false` if [elements] was empty.
+     */
+    public fun addAll(elements: Iterable<E>): Boolean {
+        val oldSize = _size
+        plusAssign(elements)
+        return oldSize != _size
+    }
+
+    /**
+     * Adds all [elements] to the end of the [MutableObjectList] and returns `true` if the
+     * [MutableObjectList] was changed or `false` if [elements] was empty.
+     */
+    public fun addAll(elements: Sequence<E>): Boolean {
+        val oldSize = _size
+        plusAssign(elements)
+        return oldSize != _size
+    }
+
+    /**
+     * Adds all [elements] to the end of the [MutableObjectList].
+     */
+    public operator fun plusAssign(elements: ObjectList<E>) {
+        if (elements.isEmpty()) return
+        ensureCapacity(_size + elements._size)
+        val content = content
+        elements.content.copyInto(
+            destination = content,
+            destinationOffset = _size,
+            startIndex = 0,
+            endIndex = elements._size
+        )
+        _size += elements._size
+    }
+
+    /**
+     * Adds all [elements] to the end of the [MutableObjectList].
+     */
+    public operator fun plusAssign(elements: ScatterSet<E>) {
+        if (elements.isEmpty()) return
+        ensureCapacity(_size + elements.size)
+        elements.forEach { element ->
+            plusAssign(element)
+        }
+    }
+
+    /**
+     * Adds all [elements] to the end of the [MutableObjectList].
+     */
+    public operator fun plusAssign(@Suppress("ArrayReturn") elements: Array<E>) {
+        if (elements.isEmpty()) return
+        ensureCapacity(_size + elements.size)
+        val content = content
+        elements.copyInto(content, _size)
+        _size += elements.size
+    }
+
+    /**
+     * Adds all [elements] to the end of the [MutableObjectList].
+     */
+    public operator fun plusAssign(elements: List<E>) {
+        if (elements.isEmpty()) return
+        val size = _size
+        ensureCapacity(size + elements.size)
+        val content = content
+        for (i in elements.indices) {
+            content[i + size] = elements[i]
+        }
+        _size += elements.size
+    }
+
+    /**
+     * Adds all [elements] to the end of the [MutableObjectList].
+     */
+    public operator fun plusAssign(elements: Iterable<E>) {
+        elements.forEach { element ->
+            plusAssign(element)
+        }
+    }
+
+    /**
+     * Adds all [elements] to the end of the [MutableObjectList].
+     */
+    public operator fun plusAssign(elements: Sequence<E>) {
+        elements.forEach { element ->
+            plusAssign(element)
+        }
+    }
+
+    /**
+     * Removes all elements in the [MutableObjectList]. The storage isn't released.
+     * @see trim
+     */
+    public fun clear() {
+        content.fill(null, fromIndex = 0, toIndex = _size)
+        _size = 0
+    }
+
+    /**
+     * Reduces the internal storage. If [capacity] is greater than [minCapacity] and [size], the
+     * internal storage is reduced to the maximum of [size] and [minCapacity].
+     * @see ensureCapacity
+     */
+    public fun trim(minCapacity: Int = _size) {
+        val minSize = maxOf(minCapacity, _size)
+        if (capacity > minSize) {
+            content = content.copyOf(minSize)
+        }
+    }
+
+    /**
+     * Ensures that there is enough space to store [capacity] elements in the [MutableObjectList].
+     * @see trim
+     */
+    public fun ensureCapacity(capacity: Int) {
+        val oldContent = content
+        if (oldContent.size < capacity) {
+            val newSize = maxOf(capacity, oldContent.size * 3 / 2)
+            content = oldContent.copyOf(newSize)
+        }
+    }
+
+    /**
+     * [add] [element] to the [MutableObjectList].
+     */
+    public inline operator fun plusAssign(element: E) {
+        add(element)
+    }
+
+    /**
+     * [remove] [element] from the [MutableObjectList]
+     */
+    public inline operator fun minusAssign(element: E) {
+        remove(element)
+    }
+
+    /**
+     * Removes [element] from the [MutableObjectList]. If [element] was in the [MutableObjectList]
+     * and was removed, `true` will be returned, or `false` will be returned if the element
+     * was not found.
+     */
+    public fun remove(element: E): Boolean {
+        val index = indexOf(element)
+        if (index >= 0) {
+            removeAt(index)
+            return true
+        }
+        return false
+    }
+
+    /**
+     * Removes all elements in this list for which [predicate] returns `true`.
+     */
+    public inline fun removeIf(predicate: (element: E) -> Boolean) {
+        var gap = 0
+        val size = _size
+        val content = content
+        for (i in indices) {
+            content[i - gap] = content[i]
+            if (predicate(content[i] as E)) {
+                gap++
+            }
+        }
+        content.fill(null, fromIndex = size - gap, toIndex = size)
+        _size -= gap
+    }
+
+    /**
+     * Removes all [elements] from the [MutableObjectList] and returns `true` if anything was removed.
+     */
+    public fun removeAll(@Suppress("ArrayReturn") elements: Array<E>): Boolean {
+        val initialSize = _size
+        for (i in elements.indices) {
+            remove(elements[i])
+        }
+        return initialSize != _size
+    }
+
+    /**
+     * Removes all [elements] from the [MutableObjectList] and returns `true` if anything was removed.
+     */
+    public fun removeAll(elements: ObjectList<E>): Boolean {
+        val initialSize = _size
+        minusAssign(elements)
+        return initialSize != _size
+    }
+
+    /**
+     * Removes all [elements] from the [MutableObjectList] and returns `true` if anything was removed.
+     */
+    public fun removeAll(elements: ScatterSet<E>): Boolean {
+        val initialSize = _size
+        minusAssign(elements)
+        return initialSize != _size
+    }
+
+    /**
+     * Removes all [elements] from the [MutableObjectList] and returns `true` if anything was removed.
+     */
+    public fun removeAll(elements: List<E>): Boolean {
+        val initialSize = _size
+        minusAssign(elements)
+        return initialSize != _size
+    }
+
+    /**
+     * Removes all [elements] from the [MutableObjectList] and returns `true` if anything was removed.
+     */
+    public fun removeAll(elements: Iterable<E>): Boolean {
+        val initialSize = _size
+        minusAssign(elements)
+        return initialSize != _size
+    }
+
+    /**
+     * Removes all [elements] from the [MutableObjectList] and returns `true` if anything was removed.
+     */
+    public fun removeAll(elements: Sequence<E>): Boolean {
+        val initialSize = _size
+        minusAssign(elements)
+        return initialSize != _size
+    }
+
+    /**
+     * Removes all [elements] from the [MutableObjectList].
+     */
+    public operator fun minusAssign(@Suppress("ArrayReturn") elements: Array<E>) {
+        elements.forEach { element ->
+            minusAssign(element)
+        }
+    }
+
+    /**
+     * Removes all [elements] from the [MutableObjectList].
+     */
+    public operator fun minusAssign(elements: ObjectList<E>) {
+        elements.forEach { element ->
+            minusAssign(element)
+        }
+    }
+
+    /**
+     * Removes all [elements] from the [MutableObjectList].
+     */
+    public operator fun minusAssign(elements: ScatterSet<E>) {
+        elements.forEach { element ->
+            minusAssign(element)
+        }
+    }
+
+    /**
+     * Removes all [elements] from the [MutableObjectList].
+     */
+    public operator fun minusAssign(elements: List<E>) {
+        for (i in elements.indices) {
+            minusAssign(elements[i])
+        }
+    }
+
+    /**
+     * Removes all [elements] from the [MutableObjectList].
+     */
+    public operator fun minusAssign(elements: Iterable<E>) {
+        elements.forEach { element ->
+            minusAssign(element)
+        }
+    }
+
+    /**
+     * Removes all [elements] from the [MutableObjectList].
+     */
+    public operator fun minusAssign(elements: Sequence<E>) {
+        elements.forEach { element ->
+            minusAssign(element)
+        }
+    }
+
+    /**
+     * Removes the element at the given [index] and returns it.
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [lastIndex], inclusive
+     */
+    public fun removeAt(@androidx.annotation.IntRange(from = 0) index: Int): E {
+        if (index !in 0 until _size) {
+            throw IndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+        }
+        val content = content
+        val element = content[index]
+        if (index != lastIndex) {
+            content.copyInto(
+                destination = content,
+                destinationOffset = index,
+                startIndex = index + 1,
+                endIndex = _size
+            )
+        }
+        _size--
+        content[_size] = null
+        return element as E
+    }
+
+    /**
+     * Removes elements from index [start] (inclusive) to [end] (exclusive).
+     * @throws IndexOutOfBoundsException if [start] or [end] isn't between 0 and [size], inclusive
+     * @throws IllegalArgumentException if [start] is greater than [end]
+     */
+    public fun removeRange(
+        @androidx.annotation.IntRange(from = 0) start: Int,
+        @androidx.annotation.IntRange(from = 0) end: Int
+    ) {
+        if (start !in 0.._size || end !in 0.._size) {
+            throw IndexOutOfBoundsException("Start ($start) and end ($end) must be in 0..$_size")
+        }
+        if (end < start) {
+            throw IllegalArgumentException("Start ($start) is more than end ($end)")
+        }
+        if (end != start) {
+            if (end < _size) {
+                content.copyInto(
+                    destination = content,
+                    destinationOffset = start,
+                    startIndex = end,
+                    endIndex = _size
+                )
+            }
+            val newSize = _size - (end - start)
+            content.fill(null, fromIndex = newSize, toIndex = _size)
+            _size = newSize
+        }
+    }
+
+    /**
+     * Keeps only [elements] in the [MutableObjectList] and removes all other values.
+     * @return `true` if the [MutableObjectList] has changed.
+     */
+    public fun retainAll(@Suppress("ArrayReturn") elements: Array<E>): Boolean {
+        val initialSize = _size
+        val content = content
+        for (i in lastIndex downTo 0) {
+            val element = content[i]
+            if (elements.indexOfFirst { it == element } < 0) {
+                removeAt(i)
+            }
+        }
+        return initialSize != _size
+    }
+
+    /**
+     * Keeps only [elements] in the [MutableObjectList] and removes all other values.
+     * @return `true` if the [MutableObjectList] has changed.
+     */
+    public fun retainAll(elements: ObjectList<E>): Boolean {
+        val initialSize = _size
+        val content = content
+        for (i in lastIndex downTo 0) {
+            val element = content[i] as E
+            if (element !in elements) {
+                removeAt(i)
+            }
+        }
+        return initialSize != _size
+    }
+
+    /**
+     * Keeps only [elements] in the [MutableObjectList] and removes all other values.
+     * @return `true` if the [MutableObjectList] has changed.
+     */
+    public fun retainAll(elements: Collection<E>): Boolean {
+        val initialSize = _size
+        val content = content
+        for (i in lastIndex downTo 0) {
+            val element = content[i] as E
+            if (element !in elements) {
+                removeAt(i)
+            }
+        }
+        return initialSize != _size
+    }
+
+    /**
+     * Keeps only [elements] in the [MutableObjectList] and removes all other values.
+     * @return `true` if the [MutableObjectList] has changed.
+     */
+    public fun retainAll(elements: Iterable<E>): Boolean {
+        val initialSize = _size
+        val content = content
+        for (i in lastIndex downTo 0) {
+            val element = content[i] as E
+            if (element !in elements) {
+                removeAt(i)
+            }
+        }
+        return initialSize != _size
+    }
+
+    /**
+     * Keeps only [elements] in the [MutableObjectList] and removes all other values.
+     * @return `true` if the [MutableObjectList] has changed.
+     */
+    public fun retainAll(elements: Sequence<E>): Boolean {
+        val initialSize = _size
+        val content = content
+        for (i in lastIndex downTo 0) {
+            val element = content[i] as E
+            if (element !in elements) {
+                removeAt(i)
+            }
+        }
+        return initialSize != _size
+    }
+
+    /**
+     * Sets the value at [index] to [element].
+     * @return the previous value set at [index]
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [lastIndex], inclusive
+     */
+    public operator fun set(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        element: E
+    ): E {
+        if (index !in 0 until _size) {
+            throw IndexOutOfBoundsException("set index $index must be between 0 .. $lastIndex")
+        }
+        val content = content
+        val old = content[index]
+        content[index] = element
+        return old as E
+    }
+
+    override fun asList(): List<E> = asMutableList()
+
+    /**
+     * Returns a [MutableList] view into the [MutableObjectList]. All access to the collection
+     * will be less efficient and abides by the allocation requirements of the
+     * [MutableList]. For example, [MutableList.forEach] will allocate an iterator.
+     * All access will go through the more expensive interface calls. Critical performance
+     * areas should use the [MutableObjectList] API rather than [MutableList] API, when possible.
+     */
+    public fun asMutableList(): MutableList<E> = list ?: ObjectListMutableList(this).also {
+        list = it
+    }
+
+    private class MutableObjectListIterator<T>(
+        private val list: MutableList<T>,
+        private var index: Int
+    ) : MutableListIterator<T> {
+        override fun hasNext(): Boolean {
+            return index < list.size
+        }
+
+        override fun next(): T {
+            return list[index++]
+        }
+
+        override fun remove() {
+            index--
+            list.removeAt(index)
+        }
+
+        override fun hasPrevious(): Boolean {
+            return index > 0
+        }
+
+        override fun nextIndex(): Int {
+            return index
+        }
+
+        override fun previous(): T {
+            index--
+            return list[index]
+        }
+
+        override fun previousIndex(): Int {
+            return index - 1
+        }
+
+        override fun add(element: T) {
+            list.add(index, element)
+            index++
+        }
+
+        override fun set(element: T) {
+            list[index] = element
+        }
+    }
+
+    /**
+     * [MutableList] implementation for a [MutableObjectList], used in [asMutableList].
+     */
+    private class ObjectListMutableList<T>(
+        private val objectList: MutableObjectList<T>
+    ) : MutableList<T> {
+        override val size: Int
+            get() = objectList.size
+
+        override fun contains(element: T): Boolean = objectList.contains(element)
+
+        override fun containsAll(elements: Collection<T>): Boolean =
+            objectList.containsAll(elements)
+
+        override fun get(index: Int): T {
+            checkIndex(index)
+            return objectList[index]
+        }
+
+        override fun indexOf(element: T): Int = objectList.indexOf(element)
+
+        override fun isEmpty(): Boolean = objectList.isEmpty()
+
+        override fun iterator(): MutableIterator<T> = MutableObjectListIterator(this, 0)
+
+        override fun lastIndexOf(element: T): Int = objectList.lastIndexOf(element)
+
+        override fun add(element: T): Boolean = objectList.add(element)
+
+        override fun add(index: Int, element: T) = objectList.add(index, element)
+
+        override fun addAll(index: Int, elements: Collection<T>): Boolean =
+            objectList.addAll(index, elements)
+
+        override fun addAll(elements: Collection<T>): Boolean = objectList.addAll(elements)
+
+        override fun clear() = objectList.clear()
+
+        override fun listIterator(): MutableListIterator<T> = MutableObjectListIterator(this, 0)
+
+        override fun listIterator(index: Int): MutableListIterator<T> =
+            MutableObjectListIterator(this, index)
+
+        override fun remove(element: T): Boolean = objectList.remove(element)
+
+        override fun removeAll(elements: Collection<T>): Boolean = objectList.removeAll(elements)
+
+        override fun removeAt(index: Int): T {
+            checkIndex(index)
+            return objectList.removeAt(index)
+        }
+
+        override fun retainAll(elements: Collection<T>): Boolean = objectList.retainAll(elements)
+
+        override fun set(index: Int, element: T): T {
+            checkIndex(index)
+            return objectList.set(index, element)
+        }
+
+        override fun subList(fromIndex: Int, toIndex: Int): MutableList<T> {
+            checkSubIndex(fromIndex, toIndex)
+            return SubList(this, fromIndex, toIndex)
+        }
+    }
+
+    /**
+     * A view into an underlying [MutableList] that directly accesses the underlying [MutableList].
+     * This is important for the implementation of [List.subList]. A change to the [SubList]
+     * also changes the referenced [MutableList].
+     */
+    private class SubList<T>(
+        private val list: MutableList<T>,
+        private val start: Int,
+        private var end: Int
+    ) : MutableList<T> {
+        override val size: Int
+            get() = end - start
+
+        override fun contains(element: T): Boolean {
+            for (i in start until end) {
+                if (list[i] == element) {
+                    return true
+                }
+            }
+            return false
+        }
+
+        override fun containsAll(elements: Collection<T>): Boolean {
+            elements.forEach {
+                if (!contains(it)) {
+                    return false
+                }
+            }
+            return true
+        }
+
+        override fun get(index: Int): T {
+            checkIndex(index)
+            return list[index + start]
+        }
+
+        override fun indexOf(element: T): Int {
+            for (i in start until end) {
+                if (list[i] == element) {
+                    return i - start
+                }
+            }
+            return -1
+        }
+
+        override fun isEmpty(): Boolean = end == start
+
+        override fun iterator(): MutableIterator<T> = MutableObjectListIterator(this, 0)
+
+        override fun lastIndexOf(element: T): Int {
+            for (i in end - 1 downTo start) {
+                if (list[i] == element) {
+                    return i - start
+                }
+            }
+            return -1
+        }
+
+        override fun add(element: T): Boolean {
+            list.add(end++, element)
+            return true
+        }
+
+        override fun add(index: Int, element: T) {
+            list.add(index + start, element)
+            end++
+        }
+
+        override fun addAll(index: Int, elements: Collection<T>): Boolean {
+            list.addAll(index + start, elements)
+            end += elements.size
+            return elements.size > 0
+        }
+
+        override fun addAll(elements: Collection<T>): Boolean {
+            list.addAll(end, elements)
+            end += elements.size
+            return elements.size > 0
+        }
+
+        override fun clear() {
+            for (i in end - 1 downTo start) {
+                list.removeAt(i)
+            }
+            end = start
+        }
+
+        override fun listIterator(): MutableListIterator<T> = MutableObjectListIterator(this, 0)
+
+        override fun listIterator(index: Int): MutableListIterator<T> =
+            MutableObjectListIterator(this, index)
+
+        override fun remove(element: T): Boolean {
+            for (i in start until end) {
+                if (list[i] == element) {
+                    list.removeAt(i)
+                    end--
+                    return true
+                }
+            }
+            return false
+        }
+
+        override fun removeAll(elements: Collection<T>): Boolean {
+            val originalEnd = end
+            elements.forEach {
+                remove(it)
+            }
+            return originalEnd != end
+        }
+
+        override fun removeAt(index: Int): T {
+            checkIndex(index)
+            val element = list.removeAt(index + start)
+            end--
+            return element
+        }
+
+        override fun retainAll(elements: Collection<T>): Boolean {
+            val originalEnd = end
+            for (i in end - 1 downTo start) {
+                val element = list[i]
+                if (element !in elements) {
+                    list.removeAt(i)
+                    end--
+                }
+            }
+            return originalEnd != end
+        }
+
+        override fun set(index: Int, element: T): T {
+            checkIndex(index)
+            return list.set(index + start, element)
+        }
+
+        override fun subList(fromIndex: Int, toIndex: Int): MutableList<T> {
+            checkSubIndex(fromIndex, toIndex)
+            return SubList(this, fromIndex, toIndex)
+        }
+    }
+}
+
+private fun List<*>.checkIndex(index: Int) {
+    val size = size
+    if (index < 0 || index >= size) {
+        throw IndexOutOfBoundsException("Index $index is out of bounds. " +
+            "The list has $size elements.")
+    }
+}
+
+private fun List<*>.checkSubIndex(fromIndex: Int, toIndex: Int) {
+    val size = size
+    if (fromIndex > toIndex) {
+        throw IllegalArgumentException("Indices are out of order. fromIndex ($fromIndex) is " +
+            "greater than toIndex ($toIndex).")
+    }
+    if (fromIndex < 0) {
+        throw IndexOutOfBoundsException("fromIndex ($fromIndex) is less than 0.")
+    }
+    if (toIndex > size) {
+        throw IndexOutOfBoundsException(
+            "toIndex ($toIndex) is more than than the list size ($size)"
+        )
+    }
+}
+
+// Empty array used when nothing is allocated
+private val EmptyArray = arrayOfNulls<Any>(0)
+
+private val EmptyObjectList: ObjectList<Any?> = MutableObjectList(0)
+
+/**
+ * @return a read-only [ObjectList] with nothing in it.
+ */
+public fun <E> emptyObjectList(): ObjectList<E> = EmptyObjectList as ObjectList<E>
+
+/**
+ * @return a read-only [ObjectList] with nothing in it.
+ */
+public fun <E> objectListOf(): ObjectList<E> = EmptyObjectList as ObjectList<E>
+
+/**
+ * @return a new read-only [ObjectList] with [element1] as the only element in the list.
+ */
+public fun <E> objectListOf(element1: E): ObjectList<E> = mutableObjectListOf(element1)
+
+/**
+ * @return a new read-only [ObjectList] with 2 elements, [element1] and [element2], in order.
+ */
+public fun <E> objectListOf(element1: E, element2: E): ObjectList<E> =
+    mutableObjectListOf(element1, element2)
+
+/**
+ * @return a new read-only [ObjectList] with 3 elements, [element1], [element2], and [element3],
+ * in order.
+ */
+public fun <E> objectListOf(element1: E, element2: E, element3: E): ObjectList<E> =
+    mutableObjectListOf(element1, element2, element3)
+
+/**
+ * @return a new read-only [ObjectList] with [elements] in order.
+ */
+public fun <E> objectListOf(vararg elements: E): ObjectList<E> =
+    MutableObjectList<E>(elements.size).apply { plusAssign(elements as Array<E>) }
+
+/**
+ * @return a new empty [MutableObjectList] with the default capacity.
+ */
+public inline fun <E> mutableObjectListOf(): MutableObjectList<E> = MutableObjectList()
+
+/**
+ * @return a new [MutableObjectList] with [element1] as the only element in the list.
+ */
+public fun <E> mutableObjectListOf(element1: E): MutableObjectList<E> {
+    val list = MutableObjectList<E>(1)
+    list += element1
+    return list
+}
+
+/**
+ * @return a new [MutableObjectList] with 2 elements, [element1] and [element2], in order.
+ */
+public fun <E> mutableObjectListOf(element1: E, element2: E): MutableObjectList<E> {
+    val list = MutableObjectList<E>(2)
+    list += element1
+    list += element2
+    return list
+}
+
+/**
+ * @return a new [MutableObjectList] with 3 elements, [element1], [element2], and [element3],
+ * in order.
+ */
+public fun <E> mutableObjectListOf(element1: E, element2: E, element3: E): MutableObjectList<E> {
+    val list = MutableObjectList<E>(3)
+    list += element1
+    list += element2
+    list += element3
+    return list
+}
+
+/**
+ * @return a new [MutableObjectList] with the given elements, in order.
+ */
+public inline fun <E> mutableObjectListOf(vararg elements: E): MutableObjectList<E> =
+    MutableObjectList<E>(elements.size).apply { plusAssign(elements as Array<E>) }
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectLongMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectLongMap.kt
new file mode 100644
index 0000000..092855b
--- /dev/null
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectLongMap.kt
@@ -0,0 +1,855 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import androidx.collection.internal.EMPTY_OBJECTS
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyObjectLongMap = MutableObjectLongMap<Any?>(0)
+
+/**
+ * Returns an empty, read-only [ObjectLongMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <K> emptyObjectLongMap(): ObjectLongMap<K> =
+    EmptyObjectLongMap as ObjectLongMap<K>
+
+/**
+ * Returns an empty, read-only [ObjectLongMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <K> objectLongMap(): ObjectLongMap<K> =
+    EmptyObjectLongMap as ObjectLongMap<K>
+
+/**
+ * Returns a new [MutableObjectLongMap].
+ */
+public fun <K> mutableObjectLongMapOf(): MutableObjectLongMap<K> = MutableObjectLongMap()
+
+/**
+ * Returns a new [MutableObjectLongMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * the [Long] value is boxed. Use [set] for each entry instead when it is
+ * important to reduce allocations.
+ */
+public fun <K> objectLongMapOf(vararg pairs: Pair<K, Long>): ObjectLongMap<K> =
+    MutableObjectLongMap<K>(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutableObjectLongMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * the [Long] value is boxed. Use [set] for each entry instead when it is
+ * important to reduce allocations.
+ */
+public fun <K> mutableObjectLongMapOf(vararg pairs: Pair<K, Long>): MutableObjectLongMap<K> =
+    MutableObjectLongMap<K>(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [ObjectLongMap] is a container with a [Map]-like interface for keys with
+ * reference types and [Long] primitives for values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutableObjectLongMap].
+ *
+ * @see [MutableObjectLongMap]
+ * @see ScatterMap
+ */
+public sealed class ObjectLongMap<K> {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: Array<Any?> = EMPTY_OBJECTS
+
+    @PublishedApi
+    @JvmField
+    internal var values: LongArray = EmptyLongArray
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key], or `null` if such
+     * a key is not present in the map.
+     * @throws NoSuchElementException when [key] is not found
+     */
+    public operator fun get(key: K): Long {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            throw NoSuchElementException("There is no key $key in the map")
+        }
+        return values[index]
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: K, defaultValue: Long): Long {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: K, defaultValue: () -> Long): Long {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue()
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: K, value: Long) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            block(k[index] as K, v[index])
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: K) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            block(k[index] as K)
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: Long) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            block(v[index])
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (K, Long) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (K, Long) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (K, Long) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: K): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: K): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: Long): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [ObjectLongMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is ObjectLongMap<*>) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        @Suppress("UNCHECKED_CAST")
+        val o = other as ObjectLongMap<Any?>
+
+        forEach { key, value ->
+            if (value != o[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(if (key === this) "(this)" else key)
+            s.append("=")
+            s.append(value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    @PublishedApi
+    internal fun findKeyIndex(key: K): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutableObjectLongMap] is a container with a [MutableMap]-like interface for keys with
+ * reference types and [Long] primitives for values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutableObjectLongMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see MutableScatterMap
+ */
+public class MutableObjectLongMap<K>(
+    initialCapacity: Int = DefaultScatterCapacity
+) : ObjectLongMap<K>() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = arrayOfNulls(newCapacity)
+        values = LongArray(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: K, defaultValue: () -> Long): Long {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        val value = defaultValue()
+        set(key, value)
+        return value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: K, value: Long) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public fun put(key: K, value: Long) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * the [Long] value is boxed. Use [set] for each entry instead when it is
+     * important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<out Pair<K, Long>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: ObjectLongMap<K>) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that the [Pair] is allocated and the [Long] value is boxed.
+     * Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<K, Long>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * the [Long] value is boxed. Use [set] for each entry instead when it is
+     * important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<out Pair<K, Long>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: ObjectLongMap<K>): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public fun remove(key: K) {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            removeValueAt(index)
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: K, value: Long): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (K, Long) -> Boolean) {
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            if (predicate(keys[index] as K, values[index])) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: K) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: Array<out K>) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: Iterable<K>) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: Sequence<K>) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: ScatterSet<K>) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int) {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+        keys[index] = null
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        keys.fill(null, 0, _capacity)
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: K): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableObjectLongMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterMap.kt
index 1de7322..37be5a5 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterMap.kt
@@ -819,16 +819,9 @@
      * or `null` if the key was not present in the map.
      */
     public fun put(key: K, value: V): V? {
-        var index = findInsertIndex(key)
-        val oldValue = if (index < 0) {
-            index = -index
-            // New entry, we must add the key
-            keys[index] = key
-            null
-        } else {
-            // Existing entry, we can keep the key
-            values[index]
-        }
+        val index = findAbsoluteInsertIndex(key)
+        val oldValue = values[index]
+        keys[index] = key
         values[index] = value
 
         @Suppress("UNCHECKED_CAST")
@@ -1072,52 +1065,6 @@
     }
 
     /**
-     * Equivalent of [findInsertIndex] but the returned index is *negative*
-     * if insertion requires a new mapping, and positive if the value takes
-     * place of an existing mapping.
-     */
-    private fun findInsertIndex(key: K): Int {
-        val hash = hash(key)
-        val hash1 = h1(hash)
-        val hash2 = h2(hash)
-
-        val probeMask = _capacity
-        var probeOffset = hash1 and probeMask
-        var probeIndex = 0
-
-        while (true) {
-            val g = group(metadata, probeOffset)
-            var m = g.match(hash2)
-            while (m.hasNext()) {
-                val index = (probeOffset + m.get()) and probeMask
-                if (keys[index] == key) {
-                    return index
-                }
-                m = m.next()
-            }
-
-            if (g.maskEmpty() != 0L) {
-                break
-            }
-
-            probeIndex += GroupWidth
-            probeOffset = (probeOffset + probeIndex) and probeMask
-        }
-
-        var index = findFirstAvailableSlot(hash1)
-        if (growthLimit == 0 && !isDeleted(metadata, index)) {
-            adjustStorage()
-            index = findFirstAvailableSlot(hash1)
-        }
-
-        _size += 1
-        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
-        writeMetadata(index, hash2.toLong())
-
-        return -index
-    }
-
-    /**
      * Finds the first empty or deleted slot in the table in which we can
      * store a value without resizing the internal storage.
      */
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatFloatMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatFloatMapTest.kt
new file mode 100644
index 0000000..fa56a25
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatFloatMapTest.kt
@@ -0,0 +1,602 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+@Suppress("RemoveRedundantCallsOfConversionMethods")
+class FloatFloatMapTest {
+    @Test
+    fun floatFloatMap() {
+        val map = MutableFloatFloatMap()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun testEmptyFloatFloatMap() {
+        val map = emptyFloatFloatMap()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyFloatFloatMap(), map)
+    }
+
+    @Test
+    fun floatFloatMapFunction() {
+        val map = mutableFloatFloatMapOf()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutableFloatFloatMap(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun floatFloatMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutableFloatFloatMap(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun floatFloatMapPairsFunction() {
+        val map = mutableFloatFloatMapOf(
+            1f to 1f,
+            2f to 2f
+        )
+        assertEquals(2, map.size)
+        assertEquals(1f, map[1f])
+        assertEquals(2f, map[2f])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map[1f])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutableFloatFloatMap(12)
+        map[1f] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map[1f])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutableFloatFloatMap(2)
+        map[1f] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals(1f, map[1f])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutableFloatFloatMap(0)
+        map[1f] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map[1f])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+        map[1f] = 2f
+
+        assertEquals(1, map.size)
+        assertEquals(2f, map[1f])
+    }
+
+    @Test
+    fun put() {
+        val map = MutableFloatFloatMap()
+
+        map.put(1f, 1f)
+        assertEquals(1f, map[1f])
+        map.put(1f, 2f)
+        assertEquals(2f, map[1f])
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+        map[2f] = 2f
+
+        map.putAll(arrayOf(3f to 3f, 7f to 7f))
+
+        assertEquals(4, map.size)
+        assertEquals(3f, map[3f])
+        assertEquals(7f, map[7f])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutableFloatFloatMap()
+        map += 1f to 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map[1f])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutableFloatFloatMap()
+        map += arrayOf(3f to 3f, 7f to 7f)
+
+        assertEquals(2, map.size)
+        assertEquals(3f, map[3f])
+        assertEquals(7f, map[7f])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+
+        assertFailsWith<NoSuchElementException> {
+            map[2f]
+        }
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+
+        assertEquals(2f, map.getOrDefault(2f, 2f))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+
+        assertEquals(3f, map.getOrElse(3f) { 3f })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+
+        var counter = 0
+        map.getOrPut(1f) {
+            counter++
+            2f
+        }
+        assertEquals(1f, map[1f])
+        assertEquals(0, counter)
+
+        map.getOrPut(2f) {
+            counter++
+            2f
+        }
+        assertEquals(2f, map[2f])
+        assertEquals(1, counter)
+
+        map.getOrPut(2f) {
+            counter++
+            3f
+        }
+        assertEquals(2f, map[2f])
+        assertEquals(1, counter)
+
+        map.getOrPut(3f) {
+            counter++
+            3f
+        }
+        assertEquals(3f, map[3f])
+        assertEquals(2, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutableFloatFloatMap()
+        map.remove(1f)
+
+        map[1f] = 1f
+        map.remove(1f)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutableFloatFloatMap(6)
+        map[1f] = 1f
+        map[2f] = 2f
+        map[3f] = 3f
+        map[4f] = 4f
+        map[5f] = 5f
+        map[6f] = 6f
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove(1f)
+        map.remove(2f)
+        map.remove(3f)
+        map.remove(4f)
+        map.remove(5f)
+        map.remove(6f)
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map[7f] = 7f
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+        map[2f] = 2f
+        map[3f] = 3f
+        map[4f] = 4f
+        map[5f] = 5f
+        map[6f] = 6f
+
+        map.removeIf { key, _ -> key == 1f || key == 3f }
+
+        assertEquals(4, map.size)
+        assertEquals(2f, map[2f])
+        assertEquals(4f, map[4f])
+        assertEquals(5f, map[5f])
+        assertEquals(6f, map[6f])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+        map[2f] = 2f
+        map[3f] = 3f
+
+        map -= 1f
+
+        assertEquals(2, map.size)
+        assertFalse(1f in map)
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+        map[2f] = 2f
+        map[3f] = 3f
+
+        map -= floatArrayOf(3f, 2f)
+
+        assertEquals(1, map.size)
+        assertFalse(3f in map)
+        assertFalse(2f in map)
+    }
+
+    @Test
+    fun minusSet() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+        map[2f] = 2f
+        map[3f] = 3f
+
+        map -= floatSetOf(3f, 2f)
+
+        assertEquals(1, map.size)
+        assertFalse(3f in map)
+        assertFalse(2f in map)
+    }
+
+    @Test
+    fun minusList() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+        map[2f] = 2f
+        map[3f] = 3f
+
+        map -= floatListOf(3f, 2f)
+
+        assertEquals(1, map.size)
+        assertFalse(3f in map)
+        assertFalse(2f in map)
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutableFloatFloatMap()
+        assertFalse(map.remove(1f, 1f))
+
+        map[1f] = 1f
+        assertTrue(map.remove(1f, 1f))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutableFloatFloatMap()
+
+        for (i in 0 until 1700) {
+            map[i.toFloat()] = i.toFloat()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutableFloatFloatMap()
+
+            for (j in 0 until i) {
+                map[j.toFloat()] = j.toFloat()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key, value.toFloat())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutableFloatFloatMap()
+
+            for (j in 0 until i) {
+                map[j.toFloat()] = j.toFloat()
+            }
+
+            var counter = 0
+            val keys = BooleanArray(map.size)
+            map.forEachKey { key ->
+                keys[key.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            keys.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutableFloatFloatMap()
+
+            for (j in 0 until i) {
+                map[j.toFloat()] = j.toFloat()
+            }
+
+            var counter = 0
+            val values = BooleanArray(map.size)
+            map.forEachValue { value ->
+                values[value.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            values.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutableFloatFloatMap()
+
+        for (i in 0 until 32) {
+            map[i.toFloat()] = i.toFloat()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutableFloatFloatMap()
+        assertEquals("{}", map.toString())
+
+        map[1f] = 1f
+        map[2f] = 2f
+        val oneValueString = 1f.toString()
+        val twoValueString = 2f.toString()
+        val oneKeyString = 1f.toString()
+        val twoKeyString = 2f.toString()
+        assertTrue(
+            "{$oneKeyString=$oneValueString, $twoKeyString=$twoValueString}" == map.toString() ||
+                "{$twoKeyString=$twoValueString, $oneKeyString=$oneValueString}" == map.toString()
+        )
+    }
+
+    @Test
+    fun equals() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutableFloatFloatMap()
+        assertNotEquals(map, map2)
+
+        map2[1f] = 1f
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+
+        assertTrue(map.containsKey(1f))
+        assertFalse(map.containsKey(2f))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+
+        assertTrue(1f in map)
+        assertFalse(2f in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+
+        assertTrue(map.containsValue(1f))
+        assertFalse(map.containsValue(3f))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutableFloatFloatMap()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map[1f] = 1f
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutableFloatFloatMap()
+        assertEquals(0, map.count())
+
+        map[1f] = 1f
+        assertEquals(1, map.count())
+
+        map[2f] = 2f
+        map[3f] = 3f
+        map[4f] = 4f
+        map[5f] = 5f
+        map[6f] = 6f
+
+        assertEquals(2, map.count { key, _ -> key <= 2f })
+        assertEquals(0, map.count { key, _ -> key < 0f })
+    }
+
+    @Test
+    fun any() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+        map[2f] = 2f
+        map[3f] = 3f
+        map[4f] = 4f
+        map[5f] = 5f
+        map[6f] = 6f
+
+        assertTrue(map.any { key, _ -> key == 4f })
+        assertFalse(map.any { key, _ -> key < 0f })
+    }
+
+    @Test
+    fun all() {
+        val map = MutableFloatFloatMap()
+        map[1f] = 1f
+        map[2f] = 2f
+        map[3f] = 3f
+        map[4f] = 4f
+        map[5f] = 5f
+        map[6f] = 6f
+
+        assertTrue(map.all { key, value -> key > 0f && value >= 1f })
+        assertFalse(map.all { key, _ -> key < 6f })
+    }
+
+    @Test
+    fun trim() {
+        val map = MutableFloatFloatMap()
+        assertEquals(7, map.trim())
+
+        map[1f] = 1f
+        map[3f] = 3f
+
+        assertEquals(0, map.trim())
+
+        for (i in 0 until 1700) {
+            map[i.toFloat()] = i.toFloat()
+        }
+
+        assertEquals(2047, map.capacity)
+
+        // After removing these items, our capacity needs should go
+        // from 2047 down to 1023
+        for (i in 0 until 1700) {
+            if (i and 0x1 == 0x0) {
+                val s = i.toFloat()
+                map.remove(s)
+            }
+        }
+
+        assertEquals(1024, map.trim())
+        assertEquals(0, map.trim())
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatIntMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatIntMapTest.kt
new file mode 100644
index 0000000..9d24ffc
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatIntMapTest.kt
@@ -0,0 +1,602 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+@Suppress("RemoveRedundantCallsOfConversionMethods")
+class FloatIntMapTest {
+    @Test
+    fun floatIntMap() {
+        val map = MutableFloatIntMap()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun testEmptyFloatIntMap() {
+        val map = emptyFloatIntMap()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyFloatIntMap(), map)
+    }
+
+    @Test
+    fun floatIntMapFunction() {
+        val map = mutableFloatIntMapOf()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutableFloatIntMap(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun floatIntMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutableFloatIntMap(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun floatIntMapPairsFunction() {
+        val map = mutableFloatIntMapOf(
+            1f to 1,
+            2f to 2
+        )
+        assertEquals(2, map.size)
+        assertEquals(1, map[1f])
+        assertEquals(2, map[2f])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map[1f])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutableFloatIntMap(12)
+        map[1f] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map[1f])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutableFloatIntMap(2)
+        map[1f] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals(1, map[1f])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutableFloatIntMap(0)
+        map[1f] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map[1f])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+        map[1f] = 2
+
+        assertEquals(1, map.size)
+        assertEquals(2, map[1f])
+    }
+
+    @Test
+    fun put() {
+        val map = MutableFloatIntMap()
+
+        map.put(1f, 1)
+        assertEquals(1, map[1f])
+        map.put(1f, 2)
+        assertEquals(2, map[1f])
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+        map[2f] = 2
+
+        map.putAll(arrayOf(3f to 3, 7f to 7))
+
+        assertEquals(4, map.size)
+        assertEquals(3, map[3f])
+        assertEquals(7, map[7f])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutableFloatIntMap()
+        map += 1f to 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map[1f])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutableFloatIntMap()
+        map += arrayOf(3f to 3, 7f to 7)
+
+        assertEquals(2, map.size)
+        assertEquals(3, map[3f])
+        assertEquals(7, map[7f])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+
+        assertFailsWith<NoSuchElementException> {
+            map[2f]
+        }
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+
+        assertEquals(2, map.getOrDefault(2f, 2))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+
+        assertEquals(3, map.getOrElse(3f) { 3 })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+
+        var counter = 0
+        map.getOrPut(1f) {
+            counter++
+            2
+        }
+        assertEquals(1, map[1f])
+        assertEquals(0, counter)
+
+        map.getOrPut(2f) {
+            counter++
+            2
+        }
+        assertEquals(2, map[2f])
+        assertEquals(1, counter)
+
+        map.getOrPut(2f) {
+            counter++
+            3
+        }
+        assertEquals(2, map[2f])
+        assertEquals(1, counter)
+
+        map.getOrPut(3f) {
+            counter++
+            3
+        }
+        assertEquals(3, map[3f])
+        assertEquals(2, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutableFloatIntMap()
+        map.remove(1f)
+
+        map[1f] = 1
+        map.remove(1f)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutableFloatIntMap(6)
+        map[1f] = 1
+        map[2f] = 2
+        map[3f] = 3
+        map[4f] = 4
+        map[5f] = 5
+        map[6f] = 6
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove(1f)
+        map.remove(2f)
+        map.remove(3f)
+        map.remove(4f)
+        map.remove(5f)
+        map.remove(6f)
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map[7f] = 7
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+        map[2f] = 2
+        map[3f] = 3
+        map[4f] = 4
+        map[5f] = 5
+        map[6f] = 6
+
+        map.removeIf { key, _ -> key == 1f || key == 3f }
+
+        assertEquals(4, map.size)
+        assertEquals(2, map[2f])
+        assertEquals(4, map[4f])
+        assertEquals(5, map[5f])
+        assertEquals(6, map[6f])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+        map[2f] = 2
+        map[3f] = 3
+
+        map -= 1f
+
+        assertEquals(2, map.size)
+        assertFalse(1f in map)
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+        map[2f] = 2
+        map[3f] = 3
+
+        map -= floatArrayOf(3f, 2f)
+
+        assertEquals(1, map.size)
+        assertFalse(3f in map)
+        assertFalse(2f in map)
+    }
+
+    @Test
+    fun minusSet() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+        map[2f] = 2
+        map[3f] = 3
+
+        map -= floatSetOf(3f, 2f)
+
+        assertEquals(1, map.size)
+        assertFalse(3f in map)
+        assertFalse(2f in map)
+    }
+
+    @Test
+    fun minusList() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+        map[2f] = 2
+        map[3f] = 3
+
+        map -= floatListOf(3f, 2f)
+
+        assertEquals(1, map.size)
+        assertFalse(3f in map)
+        assertFalse(2f in map)
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutableFloatIntMap()
+        assertFalse(map.remove(1f, 1))
+
+        map[1f] = 1
+        assertTrue(map.remove(1f, 1))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutableFloatIntMap()
+
+        for (i in 0 until 1700) {
+            map[i.toFloat()] = i.toInt()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutableFloatIntMap()
+
+            for (j in 0 until i) {
+                map[j.toFloat()] = j.toInt()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key, value.toFloat())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutableFloatIntMap()
+
+            for (j in 0 until i) {
+                map[j.toFloat()] = j.toInt()
+            }
+
+            var counter = 0
+            val keys = BooleanArray(map.size)
+            map.forEachKey { key ->
+                keys[key.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            keys.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutableFloatIntMap()
+
+            for (j in 0 until i) {
+                map[j.toFloat()] = j.toInt()
+            }
+
+            var counter = 0
+            val values = BooleanArray(map.size)
+            map.forEachValue { value ->
+                values[value.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            values.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutableFloatIntMap()
+
+        for (i in 0 until 32) {
+            map[i.toFloat()] = i.toInt()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutableFloatIntMap()
+        assertEquals("{}", map.toString())
+
+        map[1f] = 1
+        map[2f] = 2
+        val oneValueString = 1.toString()
+        val twoValueString = 2.toString()
+        val oneKeyString = 1f.toString()
+        val twoKeyString = 2f.toString()
+        assertTrue(
+            "{$oneKeyString=$oneValueString, $twoKeyString=$twoValueString}" == map.toString() ||
+                "{$twoKeyString=$twoValueString, $oneKeyString=$oneValueString}" == map.toString()
+        )
+    }
+
+    @Test
+    fun equals() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutableFloatIntMap()
+        assertNotEquals(map, map2)
+
+        map2[1f] = 1
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+
+        assertTrue(map.containsKey(1f))
+        assertFalse(map.containsKey(2f))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+
+        assertTrue(1f in map)
+        assertFalse(2f in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+
+        assertTrue(map.containsValue(1))
+        assertFalse(map.containsValue(3))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutableFloatIntMap()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map[1f] = 1
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutableFloatIntMap()
+        assertEquals(0, map.count())
+
+        map[1f] = 1
+        assertEquals(1, map.count())
+
+        map[2f] = 2
+        map[3f] = 3
+        map[4f] = 4
+        map[5f] = 5
+        map[6f] = 6
+
+        assertEquals(2, map.count { key, _ -> key <= 2f })
+        assertEquals(0, map.count { key, _ -> key < 0f })
+    }
+
+    @Test
+    fun any() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+        map[2f] = 2
+        map[3f] = 3
+        map[4f] = 4
+        map[5f] = 5
+        map[6f] = 6
+
+        assertTrue(map.any { key, _ -> key == 4f })
+        assertFalse(map.any { key, _ -> key < 0f })
+    }
+
+    @Test
+    fun all() {
+        val map = MutableFloatIntMap()
+        map[1f] = 1
+        map[2f] = 2
+        map[3f] = 3
+        map[4f] = 4
+        map[5f] = 5
+        map[6f] = 6
+
+        assertTrue(map.all { key, value -> key > 0f && value >= 1 })
+        assertFalse(map.all { key, _ -> key < 6f })
+    }
+
+    @Test
+    fun trim() {
+        val map = MutableFloatIntMap()
+        assertEquals(7, map.trim())
+
+        map[1f] = 1
+        map[3f] = 3
+
+        assertEquals(0, map.trim())
+
+        for (i in 0 until 1700) {
+            map[i.toFloat()] = i.toInt()
+        }
+
+        assertEquals(2047, map.capacity)
+
+        // After removing these items, our capacity needs should go
+        // from 2047 down to 1023
+        for (i in 0 until 1700) {
+            if (i and 0x1 == 0x0) {
+                val s = i.toFloat()
+                map.remove(s)
+            }
+        }
+
+        assertEquals(1024, map.trim())
+        assertEquals(0, map.trim())
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
index b4e501b..12e6d66 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
@@ -15,7 +15,6 @@
  */
 package androidx.collection
 
-import kotlin.math.roundToInt
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertFailsWith
@@ -23,6 +22,14 @@
 import kotlin.test.assertNotEquals
 import kotlin.test.assertTrue
 
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
 class FloatListTest {
     private val list: MutableFloatList = mutableFloatListOf(1f, 2f, 3f, 4f, 5f)
 
@@ -81,7 +88,7 @@
 
     @Test
     fun string() {
-        assertEquals("[1.0, 2.0, 3.0, 4.0, 5.0]", list.toString())
+        assertEquals("[${1f}, ${2f}, ${3f}, ${4f}, ${5f}]", list.toString())
         assertEquals("[]", mutableFloatListOf().toString())
     }
 
@@ -335,7 +342,7 @@
 
     @Test
     fun fold() {
-        assertEquals("12345", list.fold("") { acc, i -> acc + i.roundToInt().toString() })
+        assertEquals("12345", list.fold("") { acc, i -> acc + i.toInt().toString() })
     }
 
     @Test
@@ -343,14 +350,14 @@
         assertEquals(
             "01-12-23-34-45-",
             list.foldIndexed("") { index, acc, i ->
-                "$acc$index${i.roundToInt()}-"
+                "$acc$index${i.toInt()}-"
             }
         )
     }
 
     @Test
     fun foldRight() {
-        assertEquals("54321", list.foldRight("") { i, acc -> acc + i.roundToInt().toString() })
+        assertEquals("54321", list.foldRight("") { i, acc -> acc + i.toInt().toString() })
     }
 
     @Test
@@ -358,7 +365,7 @@
         assertEquals(
             "45-34-23-12-01-",
             list.foldRightIndexed("") { index, i, acc ->
-                "$acc$index${i.roundToInt()}-"
+                "$acc$index${i.toInt()}-"
             }
         )
     }
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatLongMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatLongMapTest.kt
new file mode 100644
index 0000000..cc90959
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatLongMapTest.kt
@@ -0,0 +1,602 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+@Suppress("RemoveRedundantCallsOfConversionMethods")
+class FloatLongMapTest {
+    @Test
+    fun floatLongMap() {
+        val map = MutableFloatLongMap()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun testEmptyFloatLongMap() {
+        val map = emptyFloatLongMap()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyFloatLongMap(), map)
+    }
+
+    @Test
+    fun floatLongMapFunction() {
+        val map = mutableFloatLongMapOf()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutableFloatLongMap(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun floatLongMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutableFloatLongMap(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun floatLongMapPairsFunction() {
+        val map = mutableFloatLongMapOf(
+            1f to 1L,
+            2f to 2L
+        )
+        assertEquals(2, map.size)
+        assertEquals(1L, map[1f])
+        assertEquals(2L, map[2f])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map[1f])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutableFloatLongMap(12)
+        map[1f] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map[1f])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutableFloatLongMap(2)
+        map[1f] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals(1L, map[1f])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutableFloatLongMap(0)
+        map[1f] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map[1f])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+        map[1f] = 2L
+
+        assertEquals(1, map.size)
+        assertEquals(2L, map[1f])
+    }
+
+    @Test
+    fun put() {
+        val map = MutableFloatLongMap()
+
+        map.put(1f, 1L)
+        assertEquals(1L, map[1f])
+        map.put(1f, 2L)
+        assertEquals(2L, map[1f])
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+        map[2f] = 2L
+
+        map.putAll(arrayOf(3f to 3L, 7f to 7L))
+
+        assertEquals(4, map.size)
+        assertEquals(3L, map[3f])
+        assertEquals(7L, map[7f])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutableFloatLongMap()
+        map += 1f to 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map[1f])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutableFloatLongMap()
+        map += arrayOf(3f to 3L, 7f to 7L)
+
+        assertEquals(2, map.size)
+        assertEquals(3L, map[3f])
+        assertEquals(7L, map[7f])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+
+        assertFailsWith<NoSuchElementException> {
+            map[2f]
+        }
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+
+        assertEquals(2L, map.getOrDefault(2f, 2L))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+
+        assertEquals(3L, map.getOrElse(3f) { 3L })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+
+        var counter = 0
+        map.getOrPut(1f) {
+            counter++
+            2L
+        }
+        assertEquals(1L, map[1f])
+        assertEquals(0, counter)
+
+        map.getOrPut(2f) {
+            counter++
+            2L
+        }
+        assertEquals(2L, map[2f])
+        assertEquals(1, counter)
+
+        map.getOrPut(2f) {
+            counter++
+            3L
+        }
+        assertEquals(2L, map[2f])
+        assertEquals(1, counter)
+
+        map.getOrPut(3f) {
+            counter++
+            3L
+        }
+        assertEquals(3L, map[3f])
+        assertEquals(2, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutableFloatLongMap()
+        map.remove(1f)
+
+        map[1f] = 1L
+        map.remove(1f)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutableFloatLongMap(6)
+        map[1f] = 1L
+        map[2f] = 2L
+        map[3f] = 3L
+        map[4f] = 4L
+        map[5f] = 5L
+        map[6f] = 6L
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove(1f)
+        map.remove(2f)
+        map.remove(3f)
+        map.remove(4f)
+        map.remove(5f)
+        map.remove(6f)
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map[7f] = 7L
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+        map[2f] = 2L
+        map[3f] = 3L
+        map[4f] = 4L
+        map[5f] = 5L
+        map[6f] = 6L
+
+        map.removeIf { key, _ -> key == 1f || key == 3f }
+
+        assertEquals(4, map.size)
+        assertEquals(2L, map[2f])
+        assertEquals(4L, map[4f])
+        assertEquals(5L, map[5f])
+        assertEquals(6L, map[6f])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+        map[2f] = 2L
+        map[3f] = 3L
+
+        map -= 1f
+
+        assertEquals(2, map.size)
+        assertFalse(1f in map)
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+        map[2f] = 2L
+        map[3f] = 3L
+
+        map -= floatArrayOf(3f, 2f)
+
+        assertEquals(1, map.size)
+        assertFalse(3f in map)
+        assertFalse(2f in map)
+    }
+
+    @Test
+    fun minusSet() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+        map[2f] = 2L
+        map[3f] = 3L
+
+        map -= floatSetOf(3f, 2f)
+
+        assertEquals(1, map.size)
+        assertFalse(3f in map)
+        assertFalse(2f in map)
+    }
+
+    @Test
+    fun minusList() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+        map[2f] = 2L
+        map[3f] = 3L
+
+        map -= floatListOf(3f, 2f)
+
+        assertEquals(1, map.size)
+        assertFalse(3f in map)
+        assertFalse(2f in map)
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutableFloatLongMap()
+        assertFalse(map.remove(1f, 1L))
+
+        map[1f] = 1L
+        assertTrue(map.remove(1f, 1L))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutableFloatLongMap()
+
+        for (i in 0 until 1700) {
+            map[i.toFloat()] = i.toLong()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutableFloatLongMap()
+
+            for (j in 0 until i) {
+                map[j.toFloat()] = j.toLong()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key, value.toFloat())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutableFloatLongMap()
+
+            for (j in 0 until i) {
+                map[j.toFloat()] = j.toLong()
+            }
+
+            var counter = 0
+            val keys = BooleanArray(map.size)
+            map.forEachKey { key ->
+                keys[key.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            keys.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutableFloatLongMap()
+
+            for (j in 0 until i) {
+                map[j.toFloat()] = j.toLong()
+            }
+
+            var counter = 0
+            val values = BooleanArray(map.size)
+            map.forEachValue { value ->
+                values[value.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            values.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutableFloatLongMap()
+
+        for (i in 0 until 32) {
+            map[i.toFloat()] = i.toLong()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutableFloatLongMap()
+        assertEquals("{}", map.toString())
+
+        map[1f] = 1L
+        map[2f] = 2L
+        val oneValueString = 1L.toString()
+        val twoValueString = 2L.toString()
+        val oneKeyString = 1f.toString()
+        val twoKeyString = 2f.toString()
+        assertTrue(
+            "{$oneKeyString=$oneValueString, $twoKeyString=$twoValueString}" == map.toString() ||
+                "{$twoKeyString=$twoValueString, $oneKeyString=$oneValueString}" == map.toString()
+        )
+    }
+
+    @Test
+    fun equals() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutableFloatLongMap()
+        assertNotEquals(map, map2)
+
+        map2[1f] = 1L
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+
+        assertTrue(map.containsKey(1f))
+        assertFalse(map.containsKey(2f))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+
+        assertTrue(1f in map)
+        assertFalse(2f in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+
+        assertTrue(map.containsValue(1L))
+        assertFalse(map.containsValue(3L))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutableFloatLongMap()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map[1f] = 1L
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutableFloatLongMap()
+        assertEquals(0, map.count())
+
+        map[1f] = 1L
+        assertEquals(1, map.count())
+
+        map[2f] = 2L
+        map[3f] = 3L
+        map[4f] = 4L
+        map[5f] = 5L
+        map[6f] = 6L
+
+        assertEquals(2, map.count { key, _ -> key <= 2f })
+        assertEquals(0, map.count { key, _ -> key < 0f })
+    }
+
+    @Test
+    fun any() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+        map[2f] = 2L
+        map[3f] = 3L
+        map[4f] = 4L
+        map[5f] = 5L
+        map[6f] = 6L
+
+        assertTrue(map.any { key, _ -> key == 4f })
+        assertFalse(map.any { key, _ -> key < 0f })
+    }
+
+    @Test
+    fun all() {
+        val map = MutableFloatLongMap()
+        map[1f] = 1L
+        map[2f] = 2L
+        map[3f] = 3L
+        map[4f] = 4L
+        map[5f] = 5L
+        map[6f] = 6L
+
+        assertTrue(map.all { key, value -> key > 0f && value >= 1L })
+        assertFalse(map.all { key, _ -> key < 6f })
+    }
+
+    @Test
+    fun trim() {
+        val map = MutableFloatLongMap()
+        assertEquals(7, map.trim())
+
+        map[1f] = 1L
+        map[3f] = 3L
+
+        assertEquals(0, map.trim())
+
+        for (i in 0 until 1700) {
+            map[i.toFloat()] = i.toLong()
+        }
+
+        assertEquals(2047, map.capacity)
+
+        // After removing these items, our capacity needs should go
+        // from 2047 down to 1023
+        for (i in 0 until 1700) {
+            if (i and 0x1 == 0x0) {
+                val s = i.toFloat()
+                map.remove(s)
+            }
+        }
+
+        assertEquals(1024, map.trim())
+        assertEquals(0, map.trim())
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatObjectMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatObjectMapTest.kt
new file mode 100644
index 0000000..4a10fc7b
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatObjectMapTest.kt
@@ -0,0 +1,632 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+class FloatObjectMapTest {
+    @Test
+    fun floatObjectMap() {
+        val map = MutableFloatObjectMap<String>()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun testEmptyFloatObjectMap() {
+        val map = emptyFloatObjectMap<String>()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyFloatObjectMap<String>(), map)
+    }
+
+    @Test
+    fun floatObjectMapFunction() {
+        val map = mutableFloatObjectMapOf<String>()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutableFloatObjectMap<String>(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun floatObjectMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutableFloatObjectMap<String>(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun floatObjectMapPairsFunction() {
+        val map = mutableFloatObjectMapOf(
+            1f to "World",
+            2f to "Monde"
+        )
+        assertEquals(2, map.size)
+        assertEquals("World", map[1f])
+        assertEquals("Monde", map[2f])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutableFloatObjectMap<String>()
+        map[1f] = "World"
+
+        assertEquals(1, map.size)
+        assertEquals("World", map[1f])
+    }
+
+    @Test
+    fun insertIndex0() {
+        val map = MutableFloatObjectMap<String>()
+        map.put(1f, "World")
+        assertEquals("World", map[1f])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutableFloatObjectMap<String>(12)
+        map[1f] = "World"
+
+        assertEquals(1, map.size)
+        assertEquals("World", map[1f])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutableFloatObjectMap<String>(2)
+        map[1f] = "World"
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals("World", map[1f])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutableFloatObjectMap<String>(0)
+        map[1f] = "World"
+
+        assertEquals(1, map.size)
+        assertEquals("World", map[1f])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutableFloatObjectMap<String>()
+        map[1f] = "World"
+        map[1f] = "Monde"
+
+        assertEquals(1, map.size)
+        assertEquals("Monde", map[1f])
+    }
+
+    @Test
+    fun put() {
+        val map = MutableFloatObjectMap<String?>()
+
+        assertNull(map.put(1f, "World"))
+        assertEquals("World", map.put(1f, "Monde"))
+        assertNull(map.put(2f, null))
+        assertNull(map.put(2f, "Monde"))
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutableFloatObjectMap<String?>()
+        map[1f] = "World"
+        map[2f] = null
+
+        map.putAll(arrayOf(3f to "Welt", 7f to "Mundo"))
+
+        assertEquals(4, map.size)
+        assertEquals("Welt", map[3f])
+        assertEquals("Mundo", map[7f])
+    }
+
+    @Test
+    fun putAllMap() {
+        val map = MutableFloatObjectMap<String?>()
+        map[1f] = "World"
+        map[2f] = null
+
+        map.putAll(mutableFloatObjectMapOf(3f to "Welt", 7f to "Mundo"))
+
+        assertEquals(4, map.size)
+        assertEquals("Welt", map[3f])
+        assertEquals("Mundo", map[7f])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutableFloatObjectMap<String>()
+        map += 1f to "World"
+
+        assertEquals(1, map.size)
+        assertEquals("World", map[1f])
+    }
+
+    @Test
+    fun plusMap() {
+        val map = MutableFloatObjectMap<String>()
+        map += floatObjectMapOf(3f to "Welt", 7f to "Mundo")
+
+        assertEquals(2, map.size)
+        assertEquals("Welt", map[3f])
+        assertEquals("Mundo", map[7f])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutableFloatObjectMap<String>()
+        map += arrayOf(3f to "Welt", 7f to "Mundo")
+
+        assertEquals(2, map.size)
+        assertEquals("Welt", map[3f])
+        assertEquals("Mundo", map[7f])
+    }
+
+    @Test
+    fun nullValue() {
+        val map = MutableFloatObjectMap<String?>()
+        map[1f] = null
+
+        assertEquals(1, map.size)
+        assertNull(map[1f])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutableFloatObjectMap<String?>()
+        map[1f] = "World"
+
+        assertNull(map[2f])
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutableFloatObjectMap<String?>()
+        map[1f] = "World"
+
+        assertEquals("Monde", map.getOrDefault(2f, "Monde"))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutableFloatObjectMap<String?>()
+        map[1f] = "World"
+        map[2f] = null
+
+        assertEquals("Monde", map.getOrElse(2f) { "Monde" })
+        assertEquals("Welt", map.getOrElse(3f) { "Welt" })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutableFloatObjectMap<String?>()
+        map[1f] = "World"
+
+        var counter = 0
+        map.getOrPut(1f) {
+            counter++
+            "Monde"
+        }
+        assertEquals("World", map[1f])
+        assertEquals(0, counter)
+
+        map.getOrPut(2f) {
+            counter++
+            "Monde"
+        }
+        assertEquals("Monde", map[2f])
+        assertEquals(1, counter)
+
+        map.getOrPut(2f) {
+            counter++
+            "Welt"
+        }
+        assertEquals("Monde", map[2f])
+        assertEquals(1, counter)
+
+        map.getOrPut(3f) {
+            counter++
+            null
+        }
+        assertNull(map[3f])
+        assertEquals(2, counter)
+
+        map.getOrPut(3f) {
+            counter++
+            "Welt"
+        }
+        assertEquals("Welt", map[3f])
+        assertEquals(3, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutableFloatObjectMap<String?>()
+        assertNull(map.remove(1f))
+
+        map[1f] = "World"
+        assertEquals("World", map.remove(1f))
+        assertEquals(0, map.size)
+
+        map[1f] = null
+        assertNull(map.remove(1f))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutableFloatObjectMap<String>(6)
+        map[1f] = "World"
+        map[2f] = "Monde"
+        map[3f] = "Welt"
+        map[4f] = "Sekai"
+        map[5f] = "Mondo"
+        map[6f] = "Sesang"
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove(1f)
+        map.remove(2f)
+        map.remove(3f)
+        map.remove(4f)
+        map.remove(5f)
+        map.remove(6f)
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map[7f] = "Mundo"
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutableFloatObjectMap<String>()
+        map[1f] = "World"
+        map[2f] = "Monde"
+        map[3f] = "Welt"
+        map[4f] = "Sekai"
+        map[5f] = "Mondo"
+        map[6f] = "Sesang"
+
+        map.removeIf { key, value ->
+            key == 1f || key == 3f || value.startsWith('S')
+        }
+
+        assertEquals(2, map.size)
+        assertEquals("Monde", map[2f])
+        assertEquals("Mondo", map[5f])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutableFloatObjectMap<String>()
+        map[1f] = "World"
+        map[2f] = "Monde"
+        map[3f] = "Welt"
+
+        map -= 1f
+
+        assertEquals(2, map.size)
+        assertNull(map[1f])
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutableFloatObjectMap<String>()
+        map[1f] = "World"
+        map[2f] = "Monde"
+        map[3f] = "Welt"
+
+        map -= floatArrayOf(3f, 2f)
+
+        assertEquals(1, map.size)
+        assertNull(map[3f])
+        assertNull(map[2f])
+    }
+
+    @Test
+    fun minusSet() {
+        val map = MutableFloatObjectMap<String>()
+        map[1f] = "World"
+        map[2f] = "Monde"
+        map[3f] = "Welt"
+
+        map -= floatSetOf(3f, 2f)
+
+        assertEquals(1, map.size)
+        assertNull(map[3f])
+        assertNull(map[2f])
+    }
+
+    @Test
+    fun minusList() {
+        val map = MutableFloatObjectMap<String>()
+        map[1f] = "World"
+        map[2f] = "Monde"
+        map[3f] = "Welt"
+
+        map -= floatListOf(3f, 2f)
+
+        assertEquals(1, map.size)
+        assertNull(map[3f])
+        assertNull(map[2f])
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutableFloatObjectMap<String?>()
+        assertFalse(map.remove(1f, "World"))
+
+        map[1f] = "World"
+        assertTrue(map.remove(1f, "World"))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutableFloatObjectMap<String>()
+
+        for (i in 0 until 1700) {
+            map[i.toFloat()] = i.toString()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutableFloatObjectMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toFloat()] = j.toString()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key.toInt().toString(), value)
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutableFloatObjectMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toFloat()] = j.toString()
+            }
+
+            var counter = 0
+            map.forEachKey { key ->
+                assertEquals(key.toInt().toString(), map[key])
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutableFloatObjectMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toFloat()] = j.toString()
+            }
+
+            var counter = 0
+            map.forEachValue { value ->
+                assertNotNull(value.toIntOrNull())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutableFloatObjectMap<String>()
+
+        for (i in 0 until 32) {
+            map[i.toFloat()] = i.toString()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutableFloatObjectMap<String?>()
+        assertEquals("{}", map.toString())
+
+        map[1f] = "World"
+        map[2f] = "Monde"
+        val oneKey = 1f.toString()
+        val twoKey = 2f.toString()
+        assertTrue(
+            "{$oneKey=World, $twoKey=Monde}" == map.toString() ||
+                "{$twoKey=Monde, $oneKey=World}" == map.toString()
+        )
+
+        map.clear()
+        map[1f] = null
+        assertEquals("{$oneKey=null}", map.toString())
+
+        val selfAsValueMap = MutableFloatObjectMap<Any>()
+        selfAsValueMap[1f] = selfAsValueMap
+        assertEquals("{$oneKey=(this)}", selfAsValueMap.toString())
+    }
+
+    @Test
+    fun equals() {
+        val map = MutableFloatObjectMap<String?>()
+        map[1f] = "World"
+        map[2f] = null
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutableFloatObjectMap<String?>()
+        map2[2f] = null
+
+        assertNotEquals(map, map2)
+
+        map2[1f] = "World"
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutableFloatObjectMap<String?>()
+        map[1f] = "World"
+        map[2f] = null
+
+        assertTrue(map.containsKey(1f))
+        assertFalse(map.containsKey(3f))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutableFloatObjectMap<String?>()
+        map[1f] = "World"
+        map[2f] = null
+
+        assertTrue(1f in map)
+        assertFalse(3f in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutableFloatObjectMap<String?>()
+        map[1f] = "World"
+        map[2f] = null
+
+        assertTrue(map.containsValue("World"))
+        assertTrue(map.containsValue(null))
+        assertFalse(map.containsValue("Monde"))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutableFloatObjectMap<String?>()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map[1f] = "World"
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutableFloatObjectMap<String>()
+        assertEquals(0, map.count())
+
+        map[1f] = "World"
+        assertEquals(1, map.count())
+
+        map[2f] = "Monde"
+        map[3f] = "Welt"
+        map[4f] = "Sekai"
+        map[5f] = "Mondo"
+        map[6f] = "Sesang"
+
+        assertEquals(2, map.count { key, _ -> key < 3f })
+        assertEquals(0, map.count { key, _ -> key < 0f })
+    }
+
+    @Test
+    fun any() {
+        val map = MutableFloatObjectMap<String>()
+        map[1f] = "World"
+        map[2f] = "Monde"
+        map[3f] = "Welt"
+        map[4f] = "Sekai"
+        map[5f] = "Mondo"
+        map[6f] = "Sesang"
+
+        assertTrue(map.any { key, _ -> key > 5f })
+        assertFalse(map.any { key, _ -> key < 0f })
+    }
+
+    @Test
+    fun all() {
+        val map = MutableFloatObjectMap<String>()
+        map[1f] = "World"
+        map[2f] = "Monde"
+        map[3f] = "Welt"
+        map[4f] = "Sekai"
+        map[5f] = "Mondo"
+        map[6f] = "Sesang"
+
+        assertTrue(map.all { key, value -> key < 7f && value.length > 0 })
+        assertFalse(map.all { key, _ -> key < 6f })
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt
index 98f7b59..af07640 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt
@@ -23,6 +23,14 @@
 import kotlin.test.assertSame
 import kotlin.test.assertTrue
 
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
 class FloatSetTest {
     @Test
     fun emptyFloatSetConstructor() {
@@ -147,9 +155,9 @@
         val set = MutableFloatSet()
         set += 1f
         set += 2f
-        var element: Float = Float.NaN
-        var otherElement: Float = Float.NaN
-        set.forEach { if (element.isNaN()) element = it else otherElement = it }
+        var element: Float = -1f
+        var otherElement: Float = -1f
+        set.forEach { if (element == -1f) element = it else otherElement = it }
         assertEquals(element, set.first())
         set -= element
         assertEquals(otherElement, set.first())
@@ -333,8 +341,8 @@
         set += 1f
         set += 5f
         assertTrue(
-            "[1.0, 5.0]" == set.toString() ||
-                "[5.0, 1.0]" == set.toString()
+            "[${1f}, ${5f}]" == set.toString() ||
+                "[${5f}, ${1f}]" == set.toString()
         )
     }
 
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntFloatMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntFloatMapTest.kt
new file mode 100644
index 0000000..94c8ec2
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntFloatMapTest.kt
@@ -0,0 +1,602 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+@Suppress("RemoveRedundantCallsOfConversionMethods")
+class IntFloatMapTest {
+    @Test
+    fun intFloatMap() {
+        val map = MutableIntFloatMap()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun testEmptyIntFloatMap() {
+        val map = emptyIntFloatMap()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyIntFloatMap(), map)
+    }
+
+    @Test
+    fun intFloatMapFunction() {
+        val map = mutableIntFloatMapOf()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutableIntFloatMap(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun intFloatMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutableIntFloatMap(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun intFloatMapPairsFunction() {
+        val map = mutableIntFloatMapOf(
+            1 to 1f,
+            2 to 2f
+        )
+        assertEquals(2, map.size)
+        assertEquals(1f, map[1])
+        assertEquals(2f, map[2])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map[1])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutableIntFloatMap(12)
+        map[1] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map[1])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutableIntFloatMap(2)
+        map[1] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals(1f, map[1])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutableIntFloatMap(0)
+        map[1] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map[1])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+        map[1] = 2f
+
+        assertEquals(1, map.size)
+        assertEquals(2f, map[1])
+    }
+
+    @Test
+    fun put() {
+        val map = MutableIntFloatMap()
+
+        map.put(1, 1f)
+        assertEquals(1f, map[1])
+        map.put(1, 2f)
+        assertEquals(2f, map[1])
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+        map[2] = 2f
+
+        map.putAll(arrayOf(3 to 3f, 7 to 7f))
+
+        assertEquals(4, map.size)
+        assertEquals(3f, map[3])
+        assertEquals(7f, map[7])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutableIntFloatMap()
+        map += 1 to 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map[1])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutableIntFloatMap()
+        map += arrayOf(3 to 3f, 7 to 7f)
+
+        assertEquals(2, map.size)
+        assertEquals(3f, map[3])
+        assertEquals(7f, map[7])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+
+        assertFailsWith<NoSuchElementException> {
+            map[2]
+        }
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+
+        assertEquals(2f, map.getOrDefault(2, 2f))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+
+        assertEquals(3f, map.getOrElse(3) { 3f })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+
+        var counter = 0
+        map.getOrPut(1) {
+            counter++
+            2f
+        }
+        assertEquals(1f, map[1])
+        assertEquals(0, counter)
+
+        map.getOrPut(2) {
+            counter++
+            2f
+        }
+        assertEquals(2f, map[2])
+        assertEquals(1, counter)
+
+        map.getOrPut(2) {
+            counter++
+            3f
+        }
+        assertEquals(2f, map[2])
+        assertEquals(1, counter)
+
+        map.getOrPut(3) {
+            counter++
+            3f
+        }
+        assertEquals(3f, map[3])
+        assertEquals(2, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutableIntFloatMap()
+        map.remove(1)
+
+        map[1] = 1f
+        map.remove(1)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutableIntFloatMap(6)
+        map[1] = 1f
+        map[2] = 2f
+        map[3] = 3f
+        map[4] = 4f
+        map[5] = 5f
+        map[6] = 6f
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove(1)
+        map.remove(2)
+        map.remove(3)
+        map.remove(4)
+        map.remove(5)
+        map.remove(6)
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map[7] = 7f
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+        map[2] = 2f
+        map[3] = 3f
+        map[4] = 4f
+        map[5] = 5f
+        map[6] = 6f
+
+        map.removeIf { key, _ -> key == 1 || key == 3 }
+
+        assertEquals(4, map.size)
+        assertEquals(2f, map[2])
+        assertEquals(4f, map[4])
+        assertEquals(5f, map[5])
+        assertEquals(6f, map[6])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+        map[2] = 2f
+        map[3] = 3f
+
+        map -= 1
+
+        assertEquals(2, map.size)
+        assertFalse(1 in map)
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+        map[2] = 2f
+        map[3] = 3f
+
+        map -= intArrayOf(3, 2)
+
+        assertEquals(1, map.size)
+        assertFalse(3 in map)
+        assertFalse(2 in map)
+    }
+
+    @Test
+    fun minusSet() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+        map[2] = 2f
+        map[3] = 3f
+
+        map -= intSetOf(3, 2)
+
+        assertEquals(1, map.size)
+        assertFalse(3 in map)
+        assertFalse(2 in map)
+    }
+
+    @Test
+    fun minusList() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+        map[2] = 2f
+        map[3] = 3f
+
+        map -= intListOf(3, 2)
+
+        assertEquals(1, map.size)
+        assertFalse(3 in map)
+        assertFalse(2 in map)
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutableIntFloatMap()
+        assertFalse(map.remove(1, 1f))
+
+        map[1] = 1f
+        assertTrue(map.remove(1, 1f))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutableIntFloatMap()
+
+        for (i in 0 until 1700) {
+            map[i.toInt()] = i.toFloat()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutableIntFloatMap()
+
+            for (j in 0 until i) {
+                map[j.toInt()] = j.toFloat()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key, value.toInt())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutableIntFloatMap()
+
+            for (j in 0 until i) {
+                map[j.toInt()] = j.toFloat()
+            }
+
+            var counter = 0
+            val keys = BooleanArray(map.size)
+            map.forEachKey { key ->
+                keys[key.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            keys.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutableIntFloatMap()
+
+            for (j in 0 until i) {
+                map[j.toInt()] = j.toFloat()
+            }
+
+            var counter = 0
+            val values = BooleanArray(map.size)
+            map.forEachValue { value ->
+                values[value.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            values.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutableIntFloatMap()
+
+        for (i in 0 until 32) {
+            map[i.toInt()] = i.toFloat()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutableIntFloatMap()
+        assertEquals("{}", map.toString())
+
+        map[1] = 1f
+        map[2] = 2f
+        val oneValueString = 1f.toString()
+        val twoValueString = 2f.toString()
+        val oneKeyString = 1.toString()
+        val twoKeyString = 2.toString()
+        assertTrue(
+            "{$oneKeyString=$oneValueString, $twoKeyString=$twoValueString}" == map.toString() ||
+                "{$twoKeyString=$twoValueString, $oneKeyString=$oneValueString}" == map.toString()
+        )
+    }
+
+    @Test
+    fun equals() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutableIntFloatMap()
+        assertNotEquals(map, map2)
+
+        map2[1] = 1f
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+
+        assertTrue(map.containsKey(1))
+        assertFalse(map.containsKey(2))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+
+        assertTrue(1 in map)
+        assertFalse(2 in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+
+        assertTrue(map.containsValue(1f))
+        assertFalse(map.containsValue(3f))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutableIntFloatMap()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map[1] = 1f
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutableIntFloatMap()
+        assertEquals(0, map.count())
+
+        map[1] = 1f
+        assertEquals(1, map.count())
+
+        map[2] = 2f
+        map[3] = 3f
+        map[4] = 4f
+        map[5] = 5f
+        map[6] = 6f
+
+        assertEquals(2, map.count { key, _ -> key <= 2 })
+        assertEquals(0, map.count { key, _ -> key < 0 })
+    }
+
+    @Test
+    fun any() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+        map[2] = 2f
+        map[3] = 3f
+        map[4] = 4f
+        map[5] = 5f
+        map[6] = 6f
+
+        assertTrue(map.any { key, _ -> key == 4 })
+        assertFalse(map.any { key, _ -> key < 0 })
+    }
+
+    @Test
+    fun all() {
+        val map = MutableIntFloatMap()
+        map[1] = 1f
+        map[2] = 2f
+        map[3] = 3f
+        map[4] = 4f
+        map[5] = 5f
+        map[6] = 6f
+
+        assertTrue(map.all { key, value -> key > 0 && value >= 1f })
+        assertFalse(map.all { key, _ -> key < 6 })
+    }
+
+    @Test
+    fun trim() {
+        val map = MutableIntFloatMap()
+        assertEquals(7, map.trim())
+
+        map[1] = 1f
+        map[3] = 3f
+
+        assertEquals(0, map.trim())
+
+        for (i in 0 until 1700) {
+            map[i.toInt()] = i.toFloat()
+        }
+
+        assertEquals(2047, map.capacity)
+
+        // After removing these items, our capacity needs should go
+        // from 2047 down to 1023
+        for (i in 0 until 1700) {
+            if (i and 0x1 == 0x0) {
+                val s = i.toInt()
+                map.remove(s)
+            }
+        }
+
+        assertEquals(1024, map.trim())
+        assertEquals(0, map.trim())
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntIntMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntIntMapTest.kt
new file mode 100644
index 0000000..933c5ba
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntIntMapTest.kt
@@ -0,0 +1,602 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+@Suppress("RemoveRedundantCallsOfConversionMethods")
+class IntIntMapTest {
+    @Test
+    fun intIntMap() {
+        val map = MutableIntIntMap()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun testEmptyIntIntMap() {
+        val map = emptyIntIntMap()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyIntIntMap(), map)
+    }
+
+    @Test
+    fun intIntMapFunction() {
+        val map = mutableIntIntMapOf()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutableIntIntMap(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun intIntMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutableIntIntMap(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun intIntMapPairsFunction() {
+        val map = mutableIntIntMapOf(
+            1 to 1,
+            2 to 2
+        )
+        assertEquals(2, map.size)
+        assertEquals(1, map[1])
+        assertEquals(2, map[2])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map[1])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutableIntIntMap(12)
+        map[1] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map[1])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutableIntIntMap(2)
+        map[1] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals(1, map[1])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutableIntIntMap(0)
+        map[1] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map[1])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+        map[1] = 2
+
+        assertEquals(1, map.size)
+        assertEquals(2, map[1])
+    }
+
+    @Test
+    fun put() {
+        val map = MutableIntIntMap()
+
+        map.put(1, 1)
+        assertEquals(1, map[1])
+        map.put(1, 2)
+        assertEquals(2, map[1])
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+        map[2] = 2
+
+        map.putAll(arrayOf(3 to 3, 7 to 7))
+
+        assertEquals(4, map.size)
+        assertEquals(3, map[3])
+        assertEquals(7, map[7])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutableIntIntMap()
+        map += 1 to 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map[1])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutableIntIntMap()
+        map += arrayOf(3 to 3, 7 to 7)
+
+        assertEquals(2, map.size)
+        assertEquals(3, map[3])
+        assertEquals(7, map[7])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+
+        assertFailsWith<NoSuchElementException> {
+            map[2]
+        }
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+
+        assertEquals(2, map.getOrDefault(2, 2))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+
+        assertEquals(3, map.getOrElse(3) { 3 })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+
+        var counter = 0
+        map.getOrPut(1) {
+            counter++
+            2
+        }
+        assertEquals(1, map[1])
+        assertEquals(0, counter)
+
+        map.getOrPut(2) {
+            counter++
+            2
+        }
+        assertEquals(2, map[2])
+        assertEquals(1, counter)
+
+        map.getOrPut(2) {
+            counter++
+            3
+        }
+        assertEquals(2, map[2])
+        assertEquals(1, counter)
+
+        map.getOrPut(3) {
+            counter++
+            3
+        }
+        assertEquals(3, map[3])
+        assertEquals(2, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutableIntIntMap()
+        map.remove(1)
+
+        map[1] = 1
+        map.remove(1)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutableIntIntMap(6)
+        map[1] = 1
+        map[2] = 2
+        map[3] = 3
+        map[4] = 4
+        map[5] = 5
+        map[6] = 6
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove(1)
+        map.remove(2)
+        map.remove(3)
+        map.remove(4)
+        map.remove(5)
+        map.remove(6)
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map[7] = 7
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+        map[2] = 2
+        map[3] = 3
+        map[4] = 4
+        map[5] = 5
+        map[6] = 6
+
+        map.removeIf { key, _ -> key == 1 || key == 3 }
+
+        assertEquals(4, map.size)
+        assertEquals(2, map[2])
+        assertEquals(4, map[4])
+        assertEquals(5, map[5])
+        assertEquals(6, map[6])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+        map[2] = 2
+        map[3] = 3
+
+        map -= 1
+
+        assertEquals(2, map.size)
+        assertFalse(1 in map)
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+        map[2] = 2
+        map[3] = 3
+
+        map -= intArrayOf(3, 2)
+
+        assertEquals(1, map.size)
+        assertFalse(3 in map)
+        assertFalse(2 in map)
+    }
+
+    @Test
+    fun minusSet() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+        map[2] = 2
+        map[3] = 3
+
+        map -= intSetOf(3, 2)
+
+        assertEquals(1, map.size)
+        assertFalse(3 in map)
+        assertFalse(2 in map)
+    }
+
+    @Test
+    fun minusList() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+        map[2] = 2
+        map[3] = 3
+
+        map -= intListOf(3, 2)
+
+        assertEquals(1, map.size)
+        assertFalse(3 in map)
+        assertFalse(2 in map)
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutableIntIntMap()
+        assertFalse(map.remove(1, 1))
+
+        map[1] = 1
+        assertTrue(map.remove(1, 1))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutableIntIntMap()
+
+        for (i in 0 until 1700) {
+            map[i.toInt()] = i.toInt()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutableIntIntMap()
+
+            for (j in 0 until i) {
+                map[j.toInt()] = j.toInt()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key, value.toInt())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutableIntIntMap()
+
+            for (j in 0 until i) {
+                map[j.toInt()] = j.toInt()
+            }
+
+            var counter = 0
+            val keys = BooleanArray(map.size)
+            map.forEachKey { key ->
+                keys[key.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            keys.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutableIntIntMap()
+
+            for (j in 0 until i) {
+                map[j.toInt()] = j.toInt()
+            }
+
+            var counter = 0
+            val values = BooleanArray(map.size)
+            map.forEachValue { value ->
+                values[value.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            values.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutableIntIntMap()
+
+        for (i in 0 until 32) {
+            map[i.toInt()] = i.toInt()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutableIntIntMap()
+        assertEquals("{}", map.toString())
+
+        map[1] = 1
+        map[2] = 2
+        val oneValueString = 1.toString()
+        val twoValueString = 2.toString()
+        val oneKeyString = 1.toString()
+        val twoKeyString = 2.toString()
+        assertTrue(
+            "{$oneKeyString=$oneValueString, $twoKeyString=$twoValueString}" == map.toString() ||
+                "{$twoKeyString=$twoValueString, $oneKeyString=$oneValueString}" == map.toString()
+        )
+    }
+
+    @Test
+    fun equals() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutableIntIntMap()
+        assertNotEquals(map, map2)
+
+        map2[1] = 1
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+
+        assertTrue(map.containsKey(1))
+        assertFalse(map.containsKey(2))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+
+        assertTrue(1 in map)
+        assertFalse(2 in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+
+        assertTrue(map.containsValue(1))
+        assertFalse(map.containsValue(3))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutableIntIntMap()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map[1] = 1
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutableIntIntMap()
+        assertEquals(0, map.count())
+
+        map[1] = 1
+        assertEquals(1, map.count())
+
+        map[2] = 2
+        map[3] = 3
+        map[4] = 4
+        map[5] = 5
+        map[6] = 6
+
+        assertEquals(2, map.count { key, _ -> key <= 2 })
+        assertEquals(0, map.count { key, _ -> key < 0 })
+    }
+
+    @Test
+    fun any() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+        map[2] = 2
+        map[3] = 3
+        map[4] = 4
+        map[5] = 5
+        map[6] = 6
+
+        assertTrue(map.any { key, _ -> key == 4 })
+        assertFalse(map.any { key, _ -> key < 0 })
+    }
+
+    @Test
+    fun all() {
+        val map = MutableIntIntMap()
+        map[1] = 1
+        map[2] = 2
+        map[3] = 3
+        map[4] = 4
+        map[5] = 5
+        map[6] = 6
+
+        assertTrue(map.all { key, value -> key > 0 && value >= 1 })
+        assertFalse(map.all { key, _ -> key < 6 })
+    }
+
+    @Test
+    fun trim() {
+        val map = MutableIntIntMap()
+        assertEquals(7, map.trim())
+
+        map[1] = 1
+        map[3] = 3
+
+        assertEquals(0, map.trim())
+
+        for (i in 0 until 1700) {
+            map[i.toInt()] = i.toInt()
+        }
+
+        assertEquals(2047, map.capacity)
+
+        // After removing these items, our capacity needs should go
+        // from 2047 down to 1023
+        for (i in 0 until 1700) {
+            if (i and 0x1 == 0x0) {
+                val s = i.toInt()
+                map.remove(s)
+            }
+        }
+
+        assertEquals(1024, map.trim())
+        assertEquals(0, map.trim())
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
index 66d89af..7a613eb 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
@@ -22,6 +22,14 @@
 import kotlin.test.assertNotEquals
 import kotlin.test.assertTrue
 
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
 class IntListTest {
     private val list: MutableIntList = mutableIntListOf(1, 2, 3, 4, 5)
 
@@ -80,7 +88,7 @@
 
     @Test
     fun string() {
-        assertEquals("[1, 2, 3, 4, 5]", list.toString())
+        assertEquals("[${1}, ${2}, ${3}, ${4}, ${5}]", list.toString())
         assertEquals("[]", mutableIntListOf().toString())
     }
 
@@ -334,7 +342,7 @@
 
     @Test
     fun fold() {
-        assertEquals("12345", list.fold("") { acc, i -> acc + i.toString() })
+        assertEquals("12345", list.fold("") { acc, i -> acc + i.toInt().toString() })
     }
 
     @Test
@@ -342,14 +350,14 @@
         assertEquals(
             "01-12-23-34-45-",
             list.foldIndexed("") { index, acc, i ->
-                "$acc$index$i-"
+                "$acc$index${i.toInt()}-"
             }
         )
     }
 
     @Test
     fun foldRight() {
-        assertEquals("54321", list.foldRight("") { i, acc -> acc + i.toString() })
+        assertEquals("54321", list.foldRight("") { i, acc -> acc + i.toInt().toString() })
     }
 
     @Test
@@ -357,7 +365,7 @@
         assertEquals(
             "45-34-23-12-01-",
             list.foldRightIndexed("") { index, i, acc ->
-                "$acc$index$i-"
+                "$acc$index${i.toInt()}-"
             }
         )
     }
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntLongMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntLongMapTest.kt
new file mode 100644
index 0000000..734fb05
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntLongMapTest.kt
@@ -0,0 +1,602 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+@Suppress("RemoveRedundantCallsOfConversionMethods")
+class IntLongMapTest {
+    @Test
+    fun intLongMap() {
+        val map = MutableIntLongMap()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun testEmptyIntLongMap() {
+        val map = emptyIntLongMap()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyIntLongMap(), map)
+    }
+
+    @Test
+    fun intLongMapFunction() {
+        val map = mutableIntLongMapOf()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutableIntLongMap(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun intLongMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutableIntLongMap(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun intLongMapPairsFunction() {
+        val map = mutableIntLongMapOf(
+            1 to 1L,
+            2 to 2L
+        )
+        assertEquals(2, map.size)
+        assertEquals(1L, map[1])
+        assertEquals(2L, map[2])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map[1])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutableIntLongMap(12)
+        map[1] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map[1])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutableIntLongMap(2)
+        map[1] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals(1L, map[1])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutableIntLongMap(0)
+        map[1] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map[1])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+        map[1] = 2L
+
+        assertEquals(1, map.size)
+        assertEquals(2L, map[1])
+    }
+
+    @Test
+    fun put() {
+        val map = MutableIntLongMap()
+
+        map.put(1, 1L)
+        assertEquals(1L, map[1])
+        map.put(1, 2L)
+        assertEquals(2L, map[1])
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+        map[2] = 2L
+
+        map.putAll(arrayOf(3 to 3L, 7 to 7L))
+
+        assertEquals(4, map.size)
+        assertEquals(3L, map[3])
+        assertEquals(7L, map[7])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutableIntLongMap()
+        map += 1 to 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map[1])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutableIntLongMap()
+        map += arrayOf(3 to 3L, 7 to 7L)
+
+        assertEquals(2, map.size)
+        assertEquals(3L, map[3])
+        assertEquals(7L, map[7])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+
+        assertFailsWith<NoSuchElementException> {
+            map[2]
+        }
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+
+        assertEquals(2L, map.getOrDefault(2, 2L))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+
+        assertEquals(3L, map.getOrElse(3) { 3L })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+
+        var counter = 0
+        map.getOrPut(1) {
+            counter++
+            2L
+        }
+        assertEquals(1L, map[1])
+        assertEquals(0, counter)
+
+        map.getOrPut(2) {
+            counter++
+            2L
+        }
+        assertEquals(2L, map[2])
+        assertEquals(1, counter)
+
+        map.getOrPut(2) {
+            counter++
+            3L
+        }
+        assertEquals(2L, map[2])
+        assertEquals(1, counter)
+
+        map.getOrPut(3) {
+            counter++
+            3L
+        }
+        assertEquals(3L, map[3])
+        assertEquals(2, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutableIntLongMap()
+        map.remove(1)
+
+        map[1] = 1L
+        map.remove(1)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutableIntLongMap(6)
+        map[1] = 1L
+        map[2] = 2L
+        map[3] = 3L
+        map[4] = 4L
+        map[5] = 5L
+        map[6] = 6L
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove(1)
+        map.remove(2)
+        map.remove(3)
+        map.remove(4)
+        map.remove(5)
+        map.remove(6)
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map[7] = 7L
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+        map[2] = 2L
+        map[3] = 3L
+        map[4] = 4L
+        map[5] = 5L
+        map[6] = 6L
+
+        map.removeIf { key, _ -> key == 1 || key == 3 }
+
+        assertEquals(4, map.size)
+        assertEquals(2L, map[2])
+        assertEquals(4L, map[4])
+        assertEquals(5L, map[5])
+        assertEquals(6L, map[6])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+        map[2] = 2L
+        map[3] = 3L
+
+        map -= 1
+
+        assertEquals(2, map.size)
+        assertFalse(1 in map)
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+        map[2] = 2L
+        map[3] = 3L
+
+        map -= intArrayOf(3, 2)
+
+        assertEquals(1, map.size)
+        assertFalse(3 in map)
+        assertFalse(2 in map)
+    }
+
+    @Test
+    fun minusSet() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+        map[2] = 2L
+        map[3] = 3L
+
+        map -= intSetOf(3, 2)
+
+        assertEquals(1, map.size)
+        assertFalse(3 in map)
+        assertFalse(2 in map)
+    }
+
+    @Test
+    fun minusList() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+        map[2] = 2L
+        map[3] = 3L
+
+        map -= intListOf(3, 2)
+
+        assertEquals(1, map.size)
+        assertFalse(3 in map)
+        assertFalse(2 in map)
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutableIntLongMap()
+        assertFalse(map.remove(1, 1L))
+
+        map[1] = 1L
+        assertTrue(map.remove(1, 1L))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutableIntLongMap()
+
+        for (i in 0 until 1700) {
+            map[i.toInt()] = i.toLong()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutableIntLongMap()
+
+            for (j in 0 until i) {
+                map[j.toInt()] = j.toLong()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key, value.toInt())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutableIntLongMap()
+
+            for (j in 0 until i) {
+                map[j.toInt()] = j.toLong()
+            }
+
+            var counter = 0
+            val keys = BooleanArray(map.size)
+            map.forEachKey { key ->
+                keys[key.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            keys.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutableIntLongMap()
+
+            for (j in 0 until i) {
+                map[j.toInt()] = j.toLong()
+            }
+
+            var counter = 0
+            val values = BooleanArray(map.size)
+            map.forEachValue { value ->
+                values[value.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            values.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutableIntLongMap()
+
+        for (i in 0 until 32) {
+            map[i.toInt()] = i.toLong()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutableIntLongMap()
+        assertEquals("{}", map.toString())
+
+        map[1] = 1L
+        map[2] = 2L
+        val oneValueString = 1L.toString()
+        val twoValueString = 2L.toString()
+        val oneKeyString = 1.toString()
+        val twoKeyString = 2.toString()
+        assertTrue(
+            "{$oneKeyString=$oneValueString, $twoKeyString=$twoValueString}" == map.toString() ||
+                "{$twoKeyString=$twoValueString, $oneKeyString=$oneValueString}" == map.toString()
+        )
+    }
+
+    @Test
+    fun equals() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutableIntLongMap()
+        assertNotEquals(map, map2)
+
+        map2[1] = 1L
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+
+        assertTrue(map.containsKey(1))
+        assertFalse(map.containsKey(2))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+
+        assertTrue(1 in map)
+        assertFalse(2 in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+
+        assertTrue(map.containsValue(1L))
+        assertFalse(map.containsValue(3L))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutableIntLongMap()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map[1] = 1L
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutableIntLongMap()
+        assertEquals(0, map.count())
+
+        map[1] = 1L
+        assertEquals(1, map.count())
+
+        map[2] = 2L
+        map[3] = 3L
+        map[4] = 4L
+        map[5] = 5L
+        map[6] = 6L
+
+        assertEquals(2, map.count { key, _ -> key <= 2 })
+        assertEquals(0, map.count { key, _ -> key < 0 })
+    }
+
+    @Test
+    fun any() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+        map[2] = 2L
+        map[3] = 3L
+        map[4] = 4L
+        map[5] = 5L
+        map[6] = 6L
+
+        assertTrue(map.any { key, _ -> key == 4 })
+        assertFalse(map.any { key, _ -> key < 0 })
+    }
+
+    @Test
+    fun all() {
+        val map = MutableIntLongMap()
+        map[1] = 1L
+        map[2] = 2L
+        map[3] = 3L
+        map[4] = 4L
+        map[5] = 5L
+        map[6] = 6L
+
+        assertTrue(map.all { key, value -> key > 0 && value >= 1L })
+        assertFalse(map.all { key, _ -> key < 6 })
+    }
+
+    @Test
+    fun trim() {
+        val map = MutableIntLongMap()
+        assertEquals(7, map.trim())
+
+        map[1] = 1L
+        map[3] = 3L
+
+        assertEquals(0, map.trim())
+
+        for (i in 0 until 1700) {
+            map[i.toInt()] = i.toLong()
+        }
+
+        assertEquals(2047, map.capacity)
+
+        // After removing these items, our capacity needs should go
+        // from 2047 down to 1023
+        for (i in 0 until 1700) {
+            if (i and 0x1 == 0x0) {
+                val s = i.toInt()
+                map.remove(s)
+            }
+        }
+
+        assertEquals(1024, map.trim())
+        assertEquals(0, map.trim())
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntObjectMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntObjectMapTest.kt
new file mode 100644
index 0000000..40038d2
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntObjectMapTest.kt
@@ -0,0 +1,632 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+class IntObjectMapTest {
+    @Test
+    fun intObjectMap() {
+        val map = MutableIntObjectMap<String>()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun testEmptyIntObjectMap() {
+        val map = emptyIntObjectMap<String>()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyIntObjectMap<String>(), map)
+    }
+
+    @Test
+    fun intObjectMapFunction() {
+        val map = mutableIntObjectMapOf<String>()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutableIntObjectMap<String>(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun intObjectMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutableIntObjectMap<String>(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun intObjectMapPairsFunction() {
+        val map = mutableIntObjectMapOf(
+            1 to "World",
+            2 to "Monde"
+        )
+        assertEquals(2, map.size)
+        assertEquals("World", map[1])
+        assertEquals("Monde", map[2])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutableIntObjectMap<String>()
+        map[1] = "World"
+
+        assertEquals(1, map.size)
+        assertEquals("World", map[1])
+    }
+
+    @Test
+    fun insertIndex0() {
+        val map = MutableIntObjectMap<String>()
+        map.put(1, "World")
+        assertEquals("World", map[1])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutableIntObjectMap<String>(12)
+        map[1] = "World"
+
+        assertEquals(1, map.size)
+        assertEquals("World", map[1])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutableIntObjectMap<String>(2)
+        map[1] = "World"
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals("World", map[1])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutableIntObjectMap<String>(0)
+        map[1] = "World"
+
+        assertEquals(1, map.size)
+        assertEquals("World", map[1])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutableIntObjectMap<String>()
+        map[1] = "World"
+        map[1] = "Monde"
+
+        assertEquals(1, map.size)
+        assertEquals("Monde", map[1])
+    }
+
+    @Test
+    fun put() {
+        val map = MutableIntObjectMap<String?>()
+
+        assertNull(map.put(1, "World"))
+        assertEquals("World", map.put(1, "Monde"))
+        assertNull(map.put(2, null))
+        assertNull(map.put(2, "Monde"))
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutableIntObjectMap<String?>()
+        map[1] = "World"
+        map[2] = null
+
+        map.putAll(arrayOf(3 to "Welt", 7 to "Mundo"))
+
+        assertEquals(4, map.size)
+        assertEquals("Welt", map[3])
+        assertEquals("Mundo", map[7])
+    }
+
+    @Test
+    fun putAllMap() {
+        val map = MutableIntObjectMap<String?>()
+        map[1] = "World"
+        map[2] = null
+
+        map.putAll(mutableIntObjectMapOf(3 to "Welt", 7 to "Mundo"))
+
+        assertEquals(4, map.size)
+        assertEquals("Welt", map[3])
+        assertEquals("Mundo", map[7])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutableIntObjectMap<String>()
+        map += 1 to "World"
+
+        assertEquals(1, map.size)
+        assertEquals("World", map[1])
+    }
+
+    @Test
+    fun plusMap() {
+        val map = MutableIntObjectMap<String>()
+        map += intObjectMapOf(3 to "Welt", 7 to "Mundo")
+
+        assertEquals(2, map.size)
+        assertEquals("Welt", map[3])
+        assertEquals("Mundo", map[7])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutableIntObjectMap<String>()
+        map += arrayOf(3 to "Welt", 7 to "Mundo")
+
+        assertEquals(2, map.size)
+        assertEquals("Welt", map[3])
+        assertEquals("Mundo", map[7])
+    }
+
+    @Test
+    fun nullValue() {
+        val map = MutableIntObjectMap<String?>()
+        map[1] = null
+
+        assertEquals(1, map.size)
+        assertNull(map[1])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutableIntObjectMap<String?>()
+        map[1] = "World"
+
+        assertNull(map[2])
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutableIntObjectMap<String?>()
+        map[1] = "World"
+
+        assertEquals("Monde", map.getOrDefault(2, "Monde"))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutableIntObjectMap<String?>()
+        map[1] = "World"
+        map[2] = null
+
+        assertEquals("Monde", map.getOrElse(2) { "Monde" })
+        assertEquals("Welt", map.getOrElse(3) { "Welt" })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutableIntObjectMap<String?>()
+        map[1] = "World"
+
+        var counter = 0
+        map.getOrPut(1) {
+            counter++
+            "Monde"
+        }
+        assertEquals("World", map[1])
+        assertEquals(0, counter)
+
+        map.getOrPut(2) {
+            counter++
+            "Monde"
+        }
+        assertEquals("Monde", map[2])
+        assertEquals(1, counter)
+
+        map.getOrPut(2) {
+            counter++
+            "Welt"
+        }
+        assertEquals("Monde", map[2])
+        assertEquals(1, counter)
+
+        map.getOrPut(3) {
+            counter++
+            null
+        }
+        assertNull(map[3])
+        assertEquals(2, counter)
+
+        map.getOrPut(3) {
+            counter++
+            "Welt"
+        }
+        assertEquals("Welt", map[3])
+        assertEquals(3, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutableIntObjectMap<String?>()
+        assertNull(map.remove(1))
+
+        map[1] = "World"
+        assertEquals("World", map.remove(1))
+        assertEquals(0, map.size)
+
+        map[1] = null
+        assertNull(map.remove(1))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutableIntObjectMap<String>(6)
+        map[1] = "World"
+        map[2] = "Monde"
+        map[3] = "Welt"
+        map[4] = "Sekai"
+        map[5] = "Mondo"
+        map[6] = "Sesang"
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove(1)
+        map.remove(2)
+        map.remove(3)
+        map.remove(4)
+        map.remove(5)
+        map.remove(6)
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map[7] = "Mundo"
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutableIntObjectMap<String>()
+        map[1] = "World"
+        map[2] = "Monde"
+        map[3] = "Welt"
+        map[4] = "Sekai"
+        map[5] = "Mondo"
+        map[6] = "Sesang"
+
+        map.removeIf { key, value ->
+            key == 1 || key == 3 || value.startsWith('S')
+        }
+
+        assertEquals(2, map.size)
+        assertEquals("Monde", map[2])
+        assertEquals("Mondo", map[5])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutableIntObjectMap<String>()
+        map[1] = "World"
+        map[2] = "Monde"
+        map[3] = "Welt"
+
+        map -= 1
+
+        assertEquals(2, map.size)
+        assertNull(map[1])
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutableIntObjectMap<String>()
+        map[1] = "World"
+        map[2] = "Monde"
+        map[3] = "Welt"
+
+        map -= intArrayOf(3, 2)
+
+        assertEquals(1, map.size)
+        assertNull(map[3])
+        assertNull(map[2])
+    }
+
+    @Test
+    fun minusSet() {
+        val map = MutableIntObjectMap<String>()
+        map[1] = "World"
+        map[2] = "Monde"
+        map[3] = "Welt"
+
+        map -= intSetOf(3, 2)
+
+        assertEquals(1, map.size)
+        assertNull(map[3])
+        assertNull(map[2])
+    }
+
+    @Test
+    fun minusList() {
+        val map = MutableIntObjectMap<String>()
+        map[1] = "World"
+        map[2] = "Monde"
+        map[3] = "Welt"
+
+        map -= intListOf(3, 2)
+
+        assertEquals(1, map.size)
+        assertNull(map[3])
+        assertNull(map[2])
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutableIntObjectMap<String?>()
+        assertFalse(map.remove(1, "World"))
+
+        map[1] = "World"
+        assertTrue(map.remove(1, "World"))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutableIntObjectMap<String>()
+
+        for (i in 0 until 1700) {
+            map[i.toInt()] = i.toString()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutableIntObjectMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toInt()] = j.toString()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key.toInt().toString(), value)
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutableIntObjectMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toInt()] = j.toString()
+            }
+
+            var counter = 0
+            map.forEachKey { key ->
+                assertEquals(key.toInt().toString(), map[key])
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutableIntObjectMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toInt()] = j.toString()
+            }
+
+            var counter = 0
+            map.forEachValue { value ->
+                assertNotNull(value.toIntOrNull())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutableIntObjectMap<String>()
+
+        for (i in 0 until 32) {
+            map[i.toInt()] = i.toString()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutableIntObjectMap<String?>()
+        assertEquals("{}", map.toString())
+
+        map[1] = "World"
+        map[2] = "Monde"
+        val oneKey = 1.toString()
+        val twoKey = 2.toString()
+        assertTrue(
+            "{$oneKey=World, $twoKey=Monde}" == map.toString() ||
+                "{$twoKey=Monde, $oneKey=World}" == map.toString()
+        )
+
+        map.clear()
+        map[1] = null
+        assertEquals("{$oneKey=null}", map.toString())
+
+        val selfAsValueMap = MutableIntObjectMap<Any>()
+        selfAsValueMap[1] = selfAsValueMap
+        assertEquals("{$oneKey=(this)}", selfAsValueMap.toString())
+    }
+
+    @Test
+    fun equals() {
+        val map = MutableIntObjectMap<String?>()
+        map[1] = "World"
+        map[2] = null
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutableIntObjectMap<String?>()
+        map2[2] = null
+
+        assertNotEquals(map, map2)
+
+        map2[1] = "World"
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutableIntObjectMap<String?>()
+        map[1] = "World"
+        map[2] = null
+
+        assertTrue(map.containsKey(1))
+        assertFalse(map.containsKey(3))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutableIntObjectMap<String?>()
+        map[1] = "World"
+        map[2] = null
+
+        assertTrue(1 in map)
+        assertFalse(3 in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutableIntObjectMap<String?>()
+        map[1] = "World"
+        map[2] = null
+
+        assertTrue(map.containsValue("World"))
+        assertTrue(map.containsValue(null))
+        assertFalse(map.containsValue("Monde"))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutableIntObjectMap<String?>()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map[1] = "World"
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutableIntObjectMap<String>()
+        assertEquals(0, map.count())
+
+        map[1] = "World"
+        assertEquals(1, map.count())
+
+        map[2] = "Monde"
+        map[3] = "Welt"
+        map[4] = "Sekai"
+        map[5] = "Mondo"
+        map[6] = "Sesang"
+
+        assertEquals(2, map.count { key, _ -> key < 3 })
+        assertEquals(0, map.count { key, _ -> key < 0 })
+    }
+
+    @Test
+    fun any() {
+        val map = MutableIntObjectMap<String>()
+        map[1] = "World"
+        map[2] = "Monde"
+        map[3] = "Welt"
+        map[4] = "Sekai"
+        map[5] = "Mondo"
+        map[6] = "Sesang"
+
+        assertTrue(map.any { key, _ -> key > 5 })
+        assertFalse(map.any { key, _ -> key < 0 })
+    }
+
+    @Test
+    fun all() {
+        val map = MutableIntObjectMap<String>()
+        map[1] = "World"
+        map[2] = "Monde"
+        map[3] = "Welt"
+        map[4] = "Sekai"
+        map[5] = "Mondo"
+        map[6] = "Sesang"
+
+        assertTrue(map.all { key, value -> key < 7 && value.length > 0 })
+        assertFalse(map.all { key, _ -> key < 6 })
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt
index 9d326e1..9d55a17 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt
@@ -23,6 +23,14 @@
 import kotlin.test.assertSame
 import kotlin.test.assertTrue
 
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
 class IntSetTest {
     @Test
     fun emptyIntSetConstructor() {
@@ -147,9 +155,9 @@
         val set = MutableIntSet()
         set += 1
         set += 2
-        var element: Int = Int.MIN_VALUE
-        var otherElement: Int = Int.MIN_VALUE
-        set.forEach { if (element == Int.MIN_VALUE) element = it else otherElement = it }
+        var element: Int = -1
+        var otherElement: Int = -1
+        set.forEach { if (element == -1) element = it else otherElement = it }
         assertEquals(element, set.first())
         set -= element
         assertEquals(otherElement, set.first())
@@ -333,8 +341,8 @@
         set += 1
         set += 5
         assertTrue(
-            "[1, 5]" == set.toString() ||
-                "[5, 1]" == set.toString()
+            "[${1}, ${5}]" == set.toString() ||
+                "[${5}, ${1}]" == set.toString()
         )
     }
 
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongFloatMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongFloatMapTest.kt
new file mode 100644
index 0000000..b395753
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongFloatMapTest.kt
@@ -0,0 +1,602 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+@Suppress("RemoveRedundantCallsOfConversionMethods")
+class LongFloatMapTest {
+    @Test
+    fun longFloatMap() {
+        val map = MutableLongFloatMap()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun testEmptyLongFloatMap() {
+        val map = emptyLongFloatMap()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyLongFloatMap(), map)
+    }
+
+    @Test
+    fun longFloatMapFunction() {
+        val map = mutableLongFloatMapOf()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutableLongFloatMap(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun longFloatMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutableLongFloatMap(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun longFloatMapPairsFunction() {
+        val map = mutableLongFloatMapOf(
+            1L to 1f,
+            2L to 2f
+        )
+        assertEquals(2, map.size)
+        assertEquals(1f, map[1L])
+        assertEquals(2f, map[2L])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map[1L])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutableLongFloatMap(12)
+        map[1L] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map[1L])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutableLongFloatMap(2)
+        map[1L] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals(1f, map[1L])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutableLongFloatMap(0)
+        map[1L] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map[1L])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+        map[1L] = 2f
+
+        assertEquals(1, map.size)
+        assertEquals(2f, map[1L])
+    }
+
+    @Test
+    fun put() {
+        val map = MutableLongFloatMap()
+
+        map.put(1L, 1f)
+        assertEquals(1f, map[1L])
+        map.put(1L, 2f)
+        assertEquals(2f, map[1L])
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+        map[2L] = 2f
+
+        map.putAll(arrayOf(3L to 3f, 7L to 7f))
+
+        assertEquals(4, map.size)
+        assertEquals(3f, map[3L])
+        assertEquals(7f, map[7L])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutableLongFloatMap()
+        map += 1L to 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map[1L])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutableLongFloatMap()
+        map += arrayOf(3L to 3f, 7L to 7f)
+
+        assertEquals(2, map.size)
+        assertEquals(3f, map[3L])
+        assertEquals(7f, map[7L])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+
+        assertFailsWith<NoSuchElementException> {
+            map[2L]
+        }
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+
+        assertEquals(2f, map.getOrDefault(2L, 2f))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+
+        assertEquals(3f, map.getOrElse(3L) { 3f })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+
+        var counter = 0
+        map.getOrPut(1L) {
+            counter++
+            2f
+        }
+        assertEquals(1f, map[1L])
+        assertEquals(0, counter)
+
+        map.getOrPut(2L) {
+            counter++
+            2f
+        }
+        assertEquals(2f, map[2L])
+        assertEquals(1, counter)
+
+        map.getOrPut(2L) {
+            counter++
+            3f
+        }
+        assertEquals(2f, map[2L])
+        assertEquals(1, counter)
+
+        map.getOrPut(3L) {
+            counter++
+            3f
+        }
+        assertEquals(3f, map[3L])
+        assertEquals(2, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutableLongFloatMap()
+        map.remove(1L)
+
+        map[1L] = 1f
+        map.remove(1L)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutableLongFloatMap(6)
+        map[1L] = 1f
+        map[2L] = 2f
+        map[3L] = 3f
+        map[4L] = 4f
+        map[5L] = 5f
+        map[6L] = 6f
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove(1L)
+        map.remove(2L)
+        map.remove(3L)
+        map.remove(4L)
+        map.remove(5L)
+        map.remove(6L)
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map[7L] = 7f
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+        map[2L] = 2f
+        map[3L] = 3f
+        map[4L] = 4f
+        map[5L] = 5f
+        map[6L] = 6f
+
+        map.removeIf { key, _ -> key == 1L || key == 3L }
+
+        assertEquals(4, map.size)
+        assertEquals(2f, map[2L])
+        assertEquals(4f, map[4L])
+        assertEquals(5f, map[5L])
+        assertEquals(6f, map[6L])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+        map[2L] = 2f
+        map[3L] = 3f
+
+        map -= 1L
+
+        assertEquals(2, map.size)
+        assertFalse(1L in map)
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+        map[2L] = 2f
+        map[3L] = 3f
+
+        map -= longArrayOf(3L, 2L)
+
+        assertEquals(1, map.size)
+        assertFalse(3L in map)
+        assertFalse(2L in map)
+    }
+
+    @Test
+    fun minusSet() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+        map[2L] = 2f
+        map[3L] = 3f
+
+        map -= longSetOf(3L, 2L)
+
+        assertEquals(1, map.size)
+        assertFalse(3L in map)
+        assertFalse(2L in map)
+    }
+
+    @Test
+    fun minusList() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+        map[2L] = 2f
+        map[3L] = 3f
+
+        map -= longListOf(3L, 2L)
+
+        assertEquals(1, map.size)
+        assertFalse(3L in map)
+        assertFalse(2L in map)
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutableLongFloatMap()
+        assertFalse(map.remove(1L, 1f))
+
+        map[1L] = 1f
+        assertTrue(map.remove(1L, 1f))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutableLongFloatMap()
+
+        for (i in 0 until 1700) {
+            map[i.toLong()] = i.toFloat()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutableLongFloatMap()
+
+            for (j in 0 until i) {
+                map[j.toLong()] = j.toFloat()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key, value.toLong())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutableLongFloatMap()
+
+            for (j in 0 until i) {
+                map[j.toLong()] = j.toFloat()
+            }
+
+            var counter = 0
+            val keys = BooleanArray(map.size)
+            map.forEachKey { key ->
+                keys[key.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            keys.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutableLongFloatMap()
+
+            for (j in 0 until i) {
+                map[j.toLong()] = j.toFloat()
+            }
+
+            var counter = 0
+            val values = BooleanArray(map.size)
+            map.forEachValue { value ->
+                values[value.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            values.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutableLongFloatMap()
+
+        for (i in 0 until 32) {
+            map[i.toLong()] = i.toFloat()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutableLongFloatMap()
+        assertEquals("{}", map.toString())
+
+        map[1L] = 1f
+        map[2L] = 2f
+        val oneValueString = 1f.toString()
+        val twoValueString = 2f.toString()
+        val oneKeyString = 1L.toString()
+        val twoKeyString = 2L.toString()
+        assertTrue(
+            "{$oneKeyString=$oneValueString, $twoKeyString=$twoValueString}" == map.toString() ||
+                "{$twoKeyString=$twoValueString, $oneKeyString=$oneValueString}" == map.toString()
+        )
+    }
+
+    @Test
+    fun equals() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutableLongFloatMap()
+        assertNotEquals(map, map2)
+
+        map2[1L] = 1f
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+
+        assertTrue(map.containsKey(1L))
+        assertFalse(map.containsKey(2L))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+
+        assertTrue(1L in map)
+        assertFalse(2L in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+
+        assertTrue(map.containsValue(1f))
+        assertFalse(map.containsValue(3f))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutableLongFloatMap()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map[1L] = 1f
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutableLongFloatMap()
+        assertEquals(0, map.count())
+
+        map[1L] = 1f
+        assertEquals(1, map.count())
+
+        map[2L] = 2f
+        map[3L] = 3f
+        map[4L] = 4f
+        map[5L] = 5f
+        map[6L] = 6f
+
+        assertEquals(2, map.count { key, _ -> key <= 2L })
+        assertEquals(0, map.count { key, _ -> key < 0L })
+    }
+
+    @Test
+    fun any() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+        map[2L] = 2f
+        map[3L] = 3f
+        map[4L] = 4f
+        map[5L] = 5f
+        map[6L] = 6f
+
+        assertTrue(map.any { key, _ -> key == 4L })
+        assertFalse(map.any { key, _ -> key < 0L })
+    }
+
+    @Test
+    fun all() {
+        val map = MutableLongFloatMap()
+        map[1L] = 1f
+        map[2L] = 2f
+        map[3L] = 3f
+        map[4L] = 4f
+        map[5L] = 5f
+        map[6L] = 6f
+
+        assertTrue(map.all { key, value -> key > 0L && value >= 1f })
+        assertFalse(map.all { key, _ -> key < 6L })
+    }
+
+    @Test
+    fun trim() {
+        val map = MutableLongFloatMap()
+        assertEquals(7, map.trim())
+
+        map[1L] = 1f
+        map[3L] = 3f
+
+        assertEquals(0, map.trim())
+
+        for (i in 0 until 1700) {
+            map[i.toLong()] = i.toFloat()
+        }
+
+        assertEquals(2047, map.capacity)
+
+        // After removing these items, our capacity needs should go
+        // from 2047 down to 1023
+        for (i in 0 until 1700) {
+            if (i and 0x1 == 0x0) {
+                val s = i.toLong()
+                map.remove(s)
+            }
+        }
+
+        assertEquals(1024, map.trim())
+        assertEquals(0, map.trim())
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongIntMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongIntMapTest.kt
new file mode 100644
index 0000000..cf64c64
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongIntMapTest.kt
@@ -0,0 +1,602 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+@Suppress("RemoveRedundantCallsOfConversionMethods")
+class LongIntMapTest {
+    @Test
+    fun longIntMap() {
+        val map = MutableLongIntMap()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun testEmptyLongIntMap() {
+        val map = emptyLongIntMap()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyLongIntMap(), map)
+    }
+
+    @Test
+    fun longIntMapFunction() {
+        val map = mutableLongIntMapOf()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutableLongIntMap(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun longIntMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutableLongIntMap(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun longIntMapPairsFunction() {
+        val map = mutableLongIntMapOf(
+            1L to 1,
+            2L to 2
+        )
+        assertEquals(2, map.size)
+        assertEquals(1, map[1L])
+        assertEquals(2, map[2L])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map[1L])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutableLongIntMap(12)
+        map[1L] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map[1L])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutableLongIntMap(2)
+        map[1L] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals(1, map[1L])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutableLongIntMap(0)
+        map[1L] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map[1L])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+        map[1L] = 2
+
+        assertEquals(1, map.size)
+        assertEquals(2, map[1L])
+    }
+
+    @Test
+    fun put() {
+        val map = MutableLongIntMap()
+
+        map.put(1L, 1)
+        assertEquals(1, map[1L])
+        map.put(1L, 2)
+        assertEquals(2, map[1L])
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+        map[2L] = 2
+
+        map.putAll(arrayOf(3L to 3, 7L to 7))
+
+        assertEquals(4, map.size)
+        assertEquals(3, map[3L])
+        assertEquals(7, map[7L])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutableLongIntMap()
+        map += 1L to 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map[1L])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutableLongIntMap()
+        map += arrayOf(3L to 3, 7L to 7)
+
+        assertEquals(2, map.size)
+        assertEquals(3, map[3L])
+        assertEquals(7, map[7L])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+
+        assertFailsWith<NoSuchElementException> {
+            map[2L]
+        }
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+
+        assertEquals(2, map.getOrDefault(2L, 2))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+
+        assertEquals(3, map.getOrElse(3L) { 3 })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+
+        var counter = 0
+        map.getOrPut(1L) {
+            counter++
+            2
+        }
+        assertEquals(1, map[1L])
+        assertEquals(0, counter)
+
+        map.getOrPut(2L) {
+            counter++
+            2
+        }
+        assertEquals(2, map[2L])
+        assertEquals(1, counter)
+
+        map.getOrPut(2L) {
+            counter++
+            3
+        }
+        assertEquals(2, map[2L])
+        assertEquals(1, counter)
+
+        map.getOrPut(3L) {
+            counter++
+            3
+        }
+        assertEquals(3, map[3L])
+        assertEquals(2, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutableLongIntMap()
+        map.remove(1L)
+
+        map[1L] = 1
+        map.remove(1L)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutableLongIntMap(6)
+        map[1L] = 1
+        map[2L] = 2
+        map[3L] = 3
+        map[4L] = 4
+        map[5L] = 5
+        map[6L] = 6
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove(1L)
+        map.remove(2L)
+        map.remove(3L)
+        map.remove(4L)
+        map.remove(5L)
+        map.remove(6L)
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map[7L] = 7
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+        map[2L] = 2
+        map[3L] = 3
+        map[4L] = 4
+        map[5L] = 5
+        map[6L] = 6
+
+        map.removeIf { key, _ -> key == 1L || key == 3L }
+
+        assertEquals(4, map.size)
+        assertEquals(2, map[2L])
+        assertEquals(4, map[4L])
+        assertEquals(5, map[5L])
+        assertEquals(6, map[6L])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+        map[2L] = 2
+        map[3L] = 3
+
+        map -= 1L
+
+        assertEquals(2, map.size)
+        assertFalse(1L in map)
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+        map[2L] = 2
+        map[3L] = 3
+
+        map -= longArrayOf(3L, 2L)
+
+        assertEquals(1, map.size)
+        assertFalse(3L in map)
+        assertFalse(2L in map)
+    }
+
+    @Test
+    fun minusSet() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+        map[2L] = 2
+        map[3L] = 3
+
+        map -= longSetOf(3L, 2L)
+
+        assertEquals(1, map.size)
+        assertFalse(3L in map)
+        assertFalse(2L in map)
+    }
+
+    @Test
+    fun minusList() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+        map[2L] = 2
+        map[3L] = 3
+
+        map -= longListOf(3L, 2L)
+
+        assertEquals(1, map.size)
+        assertFalse(3L in map)
+        assertFalse(2L in map)
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutableLongIntMap()
+        assertFalse(map.remove(1L, 1))
+
+        map[1L] = 1
+        assertTrue(map.remove(1L, 1))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutableLongIntMap()
+
+        for (i in 0 until 1700) {
+            map[i.toLong()] = i.toInt()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutableLongIntMap()
+
+            for (j in 0 until i) {
+                map[j.toLong()] = j.toInt()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key, value.toLong())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutableLongIntMap()
+
+            for (j in 0 until i) {
+                map[j.toLong()] = j.toInt()
+            }
+
+            var counter = 0
+            val keys = BooleanArray(map.size)
+            map.forEachKey { key ->
+                keys[key.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            keys.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutableLongIntMap()
+
+            for (j in 0 until i) {
+                map[j.toLong()] = j.toInt()
+            }
+
+            var counter = 0
+            val values = BooleanArray(map.size)
+            map.forEachValue { value ->
+                values[value.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            values.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutableLongIntMap()
+
+        for (i in 0 until 32) {
+            map[i.toLong()] = i.toInt()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutableLongIntMap()
+        assertEquals("{}", map.toString())
+
+        map[1L] = 1
+        map[2L] = 2
+        val oneValueString = 1.toString()
+        val twoValueString = 2.toString()
+        val oneKeyString = 1L.toString()
+        val twoKeyString = 2L.toString()
+        assertTrue(
+            "{$oneKeyString=$oneValueString, $twoKeyString=$twoValueString}" == map.toString() ||
+                "{$twoKeyString=$twoValueString, $oneKeyString=$oneValueString}" == map.toString()
+        )
+    }
+
+    @Test
+    fun equals() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutableLongIntMap()
+        assertNotEquals(map, map2)
+
+        map2[1L] = 1
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+
+        assertTrue(map.containsKey(1L))
+        assertFalse(map.containsKey(2L))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+
+        assertTrue(1L in map)
+        assertFalse(2L in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+
+        assertTrue(map.containsValue(1))
+        assertFalse(map.containsValue(3))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutableLongIntMap()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map[1L] = 1
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutableLongIntMap()
+        assertEquals(0, map.count())
+
+        map[1L] = 1
+        assertEquals(1, map.count())
+
+        map[2L] = 2
+        map[3L] = 3
+        map[4L] = 4
+        map[5L] = 5
+        map[6L] = 6
+
+        assertEquals(2, map.count { key, _ -> key <= 2L })
+        assertEquals(0, map.count { key, _ -> key < 0L })
+    }
+
+    @Test
+    fun any() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+        map[2L] = 2
+        map[3L] = 3
+        map[4L] = 4
+        map[5L] = 5
+        map[6L] = 6
+
+        assertTrue(map.any { key, _ -> key == 4L })
+        assertFalse(map.any { key, _ -> key < 0L })
+    }
+
+    @Test
+    fun all() {
+        val map = MutableLongIntMap()
+        map[1L] = 1
+        map[2L] = 2
+        map[3L] = 3
+        map[4L] = 4
+        map[5L] = 5
+        map[6L] = 6
+
+        assertTrue(map.all { key, value -> key > 0L && value >= 1 })
+        assertFalse(map.all { key, _ -> key < 6L })
+    }
+
+    @Test
+    fun trim() {
+        val map = MutableLongIntMap()
+        assertEquals(7, map.trim())
+
+        map[1L] = 1
+        map[3L] = 3
+
+        assertEquals(0, map.trim())
+
+        for (i in 0 until 1700) {
+            map[i.toLong()] = i.toInt()
+        }
+
+        assertEquals(2047, map.capacity)
+
+        // After removing these items, our capacity needs should go
+        // from 2047 down to 1023
+        for (i in 0 until 1700) {
+            if (i and 0x1 == 0x0) {
+                val s = i.toLong()
+                map.remove(s)
+            }
+        }
+
+        assertEquals(1024, map.trim())
+        assertEquals(0, map.trim())
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
index 45aa039..39bde8f 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
@@ -22,6 +22,14 @@
 import kotlin.test.assertNotEquals
 import kotlin.test.assertTrue
 
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
 class LongListTest {
     private val list: MutableLongList = mutableLongListOf(1L, 2L, 3L, 4L, 5L)
 
@@ -80,7 +88,7 @@
 
     @Test
     fun string() {
-        assertEquals("[1, 2, 3, 4, 5]", list.toString())
+        assertEquals("[${1L}, ${2L}, ${3L}, ${4L}, ${5L}]", list.toString())
         assertEquals("[]", mutableLongListOf().toString())
     }
 
@@ -334,7 +342,7 @@
 
     @Test
     fun fold() {
-        assertEquals("12345", list.fold("") { acc, i -> acc + i.toString() })
+        assertEquals("12345", list.fold("") { acc, i -> acc + i.toInt().toString() })
     }
 
     @Test
@@ -342,14 +350,14 @@
         assertEquals(
             "01-12-23-34-45-",
             list.foldIndexed("") { index, acc, i ->
-                "$acc$index$i-"
+                "$acc$index${i.toInt()}-"
             }
         )
     }
 
     @Test
     fun foldRight() {
-        assertEquals("54321", list.foldRight("") { i, acc -> acc + i.toString() })
+        assertEquals("54321", list.foldRight("") { i, acc -> acc + i.toInt().toString() })
     }
 
     @Test
@@ -357,7 +365,7 @@
         assertEquals(
             "45-34-23-12-01-",
             list.foldRightIndexed("") { index, i, acc ->
-                "$acc$index$i-"
+                "$acc$index${i.toInt()}-"
             }
         )
     }
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongLongMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongLongMapTest.kt
new file mode 100644
index 0000000..b45ae5a
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongLongMapTest.kt
@@ -0,0 +1,602 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+@Suppress("RemoveRedundantCallsOfConversionMethods")
+class LongLongMapTest {
+    @Test
+    fun longLongMap() {
+        val map = MutableLongLongMap()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun testEmptyLongLongMap() {
+        val map = emptyLongLongMap()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyLongLongMap(), map)
+    }
+
+    @Test
+    fun longLongMapFunction() {
+        val map = mutableLongLongMapOf()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutableLongLongMap(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun longLongMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutableLongLongMap(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun longLongMapPairsFunction() {
+        val map = mutableLongLongMapOf(
+            1L to 1L,
+            2L to 2L
+        )
+        assertEquals(2, map.size)
+        assertEquals(1L, map[1L])
+        assertEquals(2L, map[2L])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map[1L])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutableLongLongMap(12)
+        map[1L] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map[1L])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutableLongLongMap(2)
+        map[1L] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals(1L, map[1L])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutableLongLongMap(0)
+        map[1L] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map[1L])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+        map[1L] = 2L
+
+        assertEquals(1, map.size)
+        assertEquals(2L, map[1L])
+    }
+
+    @Test
+    fun put() {
+        val map = MutableLongLongMap()
+
+        map.put(1L, 1L)
+        assertEquals(1L, map[1L])
+        map.put(1L, 2L)
+        assertEquals(2L, map[1L])
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+        map[2L] = 2L
+
+        map.putAll(arrayOf(3L to 3L, 7L to 7L))
+
+        assertEquals(4, map.size)
+        assertEquals(3L, map[3L])
+        assertEquals(7L, map[7L])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutableLongLongMap()
+        map += 1L to 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map[1L])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutableLongLongMap()
+        map += arrayOf(3L to 3L, 7L to 7L)
+
+        assertEquals(2, map.size)
+        assertEquals(3L, map[3L])
+        assertEquals(7L, map[7L])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+
+        assertFailsWith<NoSuchElementException> {
+            map[2L]
+        }
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+
+        assertEquals(2L, map.getOrDefault(2L, 2L))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+
+        assertEquals(3L, map.getOrElse(3L) { 3L })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+
+        var counter = 0
+        map.getOrPut(1L) {
+            counter++
+            2L
+        }
+        assertEquals(1L, map[1L])
+        assertEquals(0, counter)
+
+        map.getOrPut(2L) {
+            counter++
+            2L
+        }
+        assertEquals(2L, map[2L])
+        assertEquals(1, counter)
+
+        map.getOrPut(2L) {
+            counter++
+            3L
+        }
+        assertEquals(2L, map[2L])
+        assertEquals(1, counter)
+
+        map.getOrPut(3L) {
+            counter++
+            3L
+        }
+        assertEquals(3L, map[3L])
+        assertEquals(2, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutableLongLongMap()
+        map.remove(1L)
+
+        map[1L] = 1L
+        map.remove(1L)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutableLongLongMap(6)
+        map[1L] = 1L
+        map[2L] = 2L
+        map[3L] = 3L
+        map[4L] = 4L
+        map[5L] = 5L
+        map[6L] = 6L
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove(1L)
+        map.remove(2L)
+        map.remove(3L)
+        map.remove(4L)
+        map.remove(5L)
+        map.remove(6L)
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map[7L] = 7L
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+        map[2L] = 2L
+        map[3L] = 3L
+        map[4L] = 4L
+        map[5L] = 5L
+        map[6L] = 6L
+
+        map.removeIf { key, _ -> key == 1L || key == 3L }
+
+        assertEquals(4, map.size)
+        assertEquals(2L, map[2L])
+        assertEquals(4L, map[4L])
+        assertEquals(5L, map[5L])
+        assertEquals(6L, map[6L])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+        map[2L] = 2L
+        map[3L] = 3L
+
+        map -= 1L
+
+        assertEquals(2, map.size)
+        assertFalse(1L in map)
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+        map[2L] = 2L
+        map[3L] = 3L
+
+        map -= longArrayOf(3L, 2L)
+
+        assertEquals(1, map.size)
+        assertFalse(3L in map)
+        assertFalse(2L in map)
+    }
+
+    @Test
+    fun minusSet() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+        map[2L] = 2L
+        map[3L] = 3L
+
+        map -= longSetOf(3L, 2L)
+
+        assertEquals(1, map.size)
+        assertFalse(3L in map)
+        assertFalse(2L in map)
+    }
+
+    @Test
+    fun minusList() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+        map[2L] = 2L
+        map[3L] = 3L
+
+        map -= longListOf(3L, 2L)
+
+        assertEquals(1, map.size)
+        assertFalse(3L in map)
+        assertFalse(2L in map)
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutableLongLongMap()
+        assertFalse(map.remove(1L, 1L))
+
+        map[1L] = 1L
+        assertTrue(map.remove(1L, 1L))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutableLongLongMap()
+
+        for (i in 0 until 1700) {
+            map[i.toLong()] = i.toLong()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutableLongLongMap()
+
+            for (j in 0 until i) {
+                map[j.toLong()] = j.toLong()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key, value.toLong())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutableLongLongMap()
+
+            for (j in 0 until i) {
+                map[j.toLong()] = j.toLong()
+            }
+
+            var counter = 0
+            val keys = BooleanArray(map.size)
+            map.forEachKey { key ->
+                keys[key.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            keys.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutableLongLongMap()
+
+            for (j in 0 until i) {
+                map[j.toLong()] = j.toLong()
+            }
+
+            var counter = 0
+            val values = BooleanArray(map.size)
+            map.forEachValue { value ->
+                values[value.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            values.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutableLongLongMap()
+
+        for (i in 0 until 32) {
+            map[i.toLong()] = i.toLong()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutableLongLongMap()
+        assertEquals("{}", map.toString())
+
+        map[1L] = 1L
+        map[2L] = 2L
+        val oneValueString = 1L.toString()
+        val twoValueString = 2L.toString()
+        val oneKeyString = 1L.toString()
+        val twoKeyString = 2L.toString()
+        assertTrue(
+            "{$oneKeyString=$oneValueString, $twoKeyString=$twoValueString}" == map.toString() ||
+                "{$twoKeyString=$twoValueString, $oneKeyString=$oneValueString}" == map.toString()
+        )
+    }
+
+    @Test
+    fun equals() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutableLongLongMap()
+        assertNotEquals(map, map2)
+
+        map2[1L] = 1L
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+
+        assertTrue(map.containsKey(1L))
+        assertFalse(map.containsKey(2L))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+
+        assertTrue(1L in map)
+        assertFalse(2L in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+
+        assertTrue(map.containsValue(1L))
+        assertFalse(map.containsValue(3L))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutableLongLongMap()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map[1L] = 1L
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutableLongLongMap()
+        assertEquals(0, map.count())
+
+        map[1L] = 1L
+        assertEquals(1, map.count())
+
+        map[2L] = 2L
+        map[3L] = 3L
+        map[4L] = 4L
+        map[5L] = 5L
+        map[6L] = 6L
+
+        assertEquals(2, map.count { key, _ -> key <= 2L })
+        assertEquals(0, map.count { key, _ -> key < 0L })
+    }
+
+    @Test
+    fun any() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+        map[2L] = 2L
+        map[3L] = 3L
+        map[4L] = 4L
+        map[5L] = 5L
+        map[6L] = 6L
+
+        assertTrue(map.any { key, _ -> key == 4L })
+        assertFalse(map.any { key, _ -> key < 0L })
+    }
+
+    @Test
+    fun all() {
+        val map = MutableLongLongMap()
+        map[1L] = 1L
+        map[2L] = 2L
+        map[3L] = 3L
+        map[4L] = 4L
+        map[5L] = 5L
+        map[6L] = 6L
+
+        assertTrue(map.all { key, value -> key > 0L && value >= 1L })
+        assertFalse(map.all { key, _ -> key < 6L })
+    }
+
+    @Test
+    fun trim() {
+        val map = MutableLongLongMap()
+        assertEquals(7, map.trim())
+
+        map[1L] = 1L
+        map[3L] = 3L
+
+        assertEquals(0, map.trim())
+
+        for (i in 0 until 1700) {
+            map[i.toLong()] = i.toLong()
+        }
+
+        assertEquals(2047, map.capacity)
+
+        // After removing these items, our capacity needs should go
+        // from 2047 down to 1023
+        for (i in 0 until 1700) {
+            if (i and 0x1 == 0x0) {
+                val s = i.toLong()
+                map.remove(s)
+            }
+        }
+
+        assertEquals(1024, map.trim())
+        assertEquals(0, map.trim())
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongObjectMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongObjectMapTest.kt
new file mode 100644
index 0000000..1a5fd8bf
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongObjectMapTest.kt
@@ -0,0 +1,632 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+class LongObjectMapTest {
+    @Test
+    fun longObjectMap() {
+        val map = MutableLongObjectMap<String>()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun testEmptyLongObjectMap() {
+        val map = emptyLongObjectMap<String>()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyLongObjectMap<String>(), map)
+    }
+
+    @Test
+    fun longObjectMapFunction() {
+        val map = mutableLongObjectMapOf<String>()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutableLongObjectMap<String>(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun longObjectMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutableLongObjectMap<String>(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun longObjectMapPairsFunction() {
+        val map = mutableLongObjectMapOf(
+            1L to "World",
+            2L to "Monde"
+        )
+        assertEquals(2, map.size)
+        assertEquals("World", map[1L])
+        assertEquals("Monde", map[2L])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutableLongObjectMap<String>()
+        map[1L] = "World"
+
+        assertEquals(1, map.size)
+        assertEquals("World", map[1L])
+    }
+
+    @Test
+    fun insertIndex0() {
+        val map = MutableLongObjectMap<String>()
+        map.put(1L, "World")
+        assertEquals("World", map[1L])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutableLongObjectMap<String>(12)
+        map[1L] = "World"
+
+        assertEquals(1, map.size)
+        assertEquals("World", map[1L])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutableLongObjectMap<String>(2)
+        map[1L] = "World"
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals("World", map[1L])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutableLongObjectMap<String>(0)
+        map[1L] = "World"
+
+        assertEquals(1, map.size)
+        assertEquals("World", map[1L])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutableLongObjectMap<String>()
+        map[1L] = "World"
+        map[1L] = "Monde"
+
+        assertEquals(1, map.size)
+        assertEquals("Monde", map[1L])
+    }
+
+    @Test
+    fun put() {
+        val map = MutableLongObjectMap<String?>()
+
+        assertNull(map.put(1L, "World"))
+        assertEquals("World", map.put(1L, "Monde"))
+        assertNull(map.put(2L, null))
+        assertNull(map.put(2L, "Monde"))
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutableLongObjectMap<String?>()
+        map[1L] = "World"
+        map[2L] = null
+
+        map.putAll(arrayOf(3L to "Welt", 7L to "Mundo"))
+
+        assertEquals(4, map.size)
+        assertEquals("Welt", map[3L])
+        assertEquals("Mundo", map[7L])
+    }
+
+    @Test
+    fun putAllMap() {
+        val map = MutableLongObjectMap<String?>()
+        map[1L] = "World"
+        map[2L] = null
+
+        map.putAll(mutableLongObjectMapOf(3L to "Welt", 7L to "Mundo"))
+
+        assertEquals(4, map.size)
+        assertEquals("Welt", map[3L])
+        assertEquals("Mundo", map[7L])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutableLongObjectMap<String>()
+        map += 1L to "World"
+
+        assertEquals(1, map.size)
+        assertEquals("World", map[1L])
+    }
+
+    @Test
+    fun plusMap() {
+        val map = MutableLongObjectMap<String>()
+        map += longObjectMapOf(3L to "Welt", 7L to "Mundo")
+
+        assertEquals(2, map.size)
+        assertEquals("Welt", map[3L])
+        assertEquals("Mundo", map[7L])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutableLongObjectMap<String>()
+        map += arrayOf(3L to "Welt", 7L to "Mundo")
+
+        assertEquals(2, map.size)
+        assertEquals("Welt", map[3L])
+        assertEquals("Mundo", map[7L])
+    }
+
+    @Test
+    fun nullValue() {
+        val map = MutableLongObjectMap<String?>()
+        map[1L] = null
+
+        assertEquals(1, map.size)
+        assertNull(map[1L])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutableLongObjectMap<String?>()
+        map[1L] = "World"
+
+        assertNull(map[2L])
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutableLongObjectMap<String?>()
+        map[1L] = "World"
+
+        assertEquals("Monde", map.getOrDefault(2L, "Monde"))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutableLongObjectMap<String?>()
+        map[1L] = "World"
+        map[2L] = null
+
+        assertEquals("Monde", map.getOrElse(2L) { "Monde" })
+        assertEquals("Welt", map.getOrElse(3L) { "Welt" })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutableLongObjectMap<String?>()
+        map[1L] = "World"
+
+        var counter = 0
+        map.getOrPut(1L) {
+            counter++
+            "Monde"
+        }
+        assertEquals("World", map[1L])
+        assertEquals(0, counter)
+
+        map.getOrPut(2L) {
+            counter++
+            "Monde"
+        }
+        assertEquals("Monde", map[2L])
+        assertEquals(1, counter)
+
+        map.getOrPut(2L) {
+            counter++
+            "Welt"
+        }
+        assertEquals("Monde", map[2L])
+        assertEquals(1, counter)
+
+        map.getOrPut(3L) {
+            counter++
+            null
+        }
+        assertNull(map[3L])
+        assertEquals(2, counter)
+
+        map.getOrPut(3L) {
+            counter++
+            "Welt"
+        }
+        assertEquals("Welt", map[3L])
+        assertEquals(3, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutableLongObjectMap<String?>()
+        assertNull(map.remove(1L))
+
+        map[1L] = "World"
+        assertEquals("World", map.remove(1L))
+        assertEquals(0, map.size)
+
+        map[1L] = null
+        assertNull(map.remove(1L))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutableLongObjectMap<String>(6)
+        map[1L] = "World"
+        map[2L] = "Monde"
+        map[3L] = "Welt"
+        map[4L] = "Sekai"
+        map[5L] = "Mondo"
+        map[6L] = "Sesang"
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove(1L)
+        map.remove(2L)
+        map.remove(3L)
+        map.remove(4L)
+        map.remove(5L)
+        map.remove(6L)
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map[7L] = "Mundo"
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutableLongObjectMap<String>()
+        map[1L] = "World"
+        map[2L] = "Monde"
+        map[3L] = "Welt"
+        map[4L] = "Sekai"
+        map[5L] = "Mondo"
+        map[6L] = "Sesang"
+
+        map.removeIf { key, value ->
+            key == 1L || key == 3L || value.startsWith('S')
+        }
+
+        assertEquals(2, map.size)
+        assertEquals("Monde", map[2L])
+        assertEquals("Mondo", map[5L])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutableLongObjectMap<String>()
+        map[1L] = "World"
+        map[2L] = "Monde"
+        map[3L] = "Welt"
+
+        map -= 1L
+
+        assertEquals(2, map.size)
+        assertNull(map[1L])
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutableLongObjectMap<String>()
+        map[1L] = "World"
+        map[2L] = "Monde"
+        map[3L] = "Welt"
+
+        map -= longArrayOf(3L, 2L)
+
+        assertEquals(1, map.size)
+        assertNull(map[3L])
+        assertNull(map[2L])
+    }
+
+    @Test
+    fun minusSet() {
+        val map = MutableLongObjectMap<String>()
+        map[1L] = "World"
+        map[2L] = "Monde"
+        map[3L] = "Welt"
+
+        map -= longSetOf(3L, 2L)
+
+        assertEquals(1, map.size)
+        assertNull(map[3L])
+        assertNull(map[2L])
+    }
+
+    @Test
+    fun minusList() {
+        val map = MutableLongObjectMap<String>()
+        map[1L] = "World"
+        map[2L] = "Monde"
+        map[3L] = "Welt"
+
+        map -= longListOf(3L, 2L)
+
+        assertEquals(1, map.size)
+        assertNull(map[3L])
+        assertNull(map[2L])
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutableLongObjectMap<String?>()
+        assertFalse(map.remove(1L, "World"))
+
+        map[1L] = "World"
+        assertTrue(map.remove(1L, "World"))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutableLongObjectMap<String>()
+
+        for (i in 0 until 1700) {
+            map[i.toLong()] = i.toString()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutableLongObjectMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toLong()] = j.toString()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key.toInt().toString(), value)
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutableLongObjectMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toLong()] = j.toString()
+            }
+
+            var counter = 0
+            map.forEachKey { key ->
+                assertEquals(key.toInt().toString(), map[key])
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutableLongObjectMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toLong()] = j.toString()
+            }
+
+            var counter = 0
+            map.forEachValue { value ->
+                assertNotNull(value.toIntOrNull())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutableLongObjectMap<String>()
+
+        for (i in 0 until 32) {
+            map[i.toLong()] = i.toString()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutableLongObjectMap<String?>()
+        assertEquals("{}", map.toString())
+
+        map[1L] = "World"
+        map[2L] = "Monde"
+        val oneKey = 1L.toString()
+        val twoKey = 2L.toString()
+        assertTrue(
+            "{$oneKey=World, $twoKey=Monde}" == map.toString() ||
+                "{$twoKey=Monde, $oneKey=World}" == map.toString()
+        )
+
+        map.clear()
+        map[1L] = null
+        assertEquals("{$oneKey=null}", map.toString())
+
+        val selfAsValueMap = MutableLongObjectMap<Any>()
+        selfAsValueMap[1L] = selfAsValueMap
+        assertEquals("{$oneKey=(this)}", selfAsValueMap.toString())
+    }
+
+    @Test
+    fun equals() {
+        val map = MutableLongObjectMap<String?>()
+        map[1L] = "World"
+        map[2L] = null
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutableLongObjectMap<String?>()
+        map2[2L] = null
+
+        assertNotEquals(map, map2)
+
+        map2[1L] = "World"
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutableLongObjectMap<String?>()
+        map[1L] = "World"
+        map[2L] = null
+
+        assertTrue(map.containsKey(1L))
+        assertFalse(map.containsKey(3L))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutableLongObjectMap<String?>()
+        map[1L] = "World"
+        map[2L] = null
+
+        assertTrue(1L in map)
+        assertFalse(3L in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutableLongObjectMap<String?>()
+        map[1L] = "World"
+        map[2L] = null
+
+        assertTrue(map.containsValue("World"))
+        assertTrue(map.containsValue(null))
+        assertFalse(map.containsValue("Monde"))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutableLongObjectMap<String?>()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map[1L] = "World"
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutableLongObjectMap<String>()
+        assertEquals(0, map.count())
+
+        map[1L] = "World"
+        assertEquals(1, map.count())
+
+        map[2L] = "Monde"
+        map[3L] = "Welt"
+        map[4L] = "Sekai"
+        map[5L] = "Mondo"
+        map[6L] = "Sesang"
+
+        assertEquals(2, map.count { key, _ -> key < 3L })
+        assertEquals(0, map.count { key, _ -> key < 0L })
+    }
+
+    @Test
+    fun any() {
+        val map = MutableLongObjectMap<String>()
+        map[1L] = "World"
+        map[2L] = "Monde"
+        map[3L] = "Welt"
+        map[4L] = "Sekai"
+        map[5L] = "Mondo"
+        map[6L] = "Sesang"
+
+        assertTrue(map.any { key, _ -> key > 5L })
+        assertFalse(map.any { key, _ -> key < 0L })
+    }
+
+    @Test
+    fun all() {
+        val map = MutableLongObjectMap<String>()
+        map[1L] = "World"
+        map[2L] = "Monde"
+        map[3L] = "Welt"
+        map[4L] = "Sekai"
+        map[5L] = "Mondo"
+        map[6L] = "Sesang"
+
+        assertTrue(map.all { key, value -> key < 7L && value.length > 0 })
+        assertFalse(map.all { key, _ -> key < 6L })
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt
index 1278fcf..2ee0e96 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt
@@ -23,6 +23,14 @@
 import kotlin.test.assertSame
 import kotlin.test.assertTrue
 
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
 class LongSetTest {
     @Test
     fun emptyLongSetConstructor() {
@@ -147,9 +155,9 @@
         val set = MutableLongSet()
         set += 1L
         set += 2L
-        var element: Long = Long.MIN_VALUE
-        var otherElement: Long = Long.MIN_VALUE
-        set.forEach { if (element == Long.MIN_VALUE) element = it else otherElement = it }
+        var element: Long = -1L
+        var otherElement: Long = -1L
+        set.forEach { if (element == -1L) element = it else otherElement = it }
         assertEquals(element, set.first())
         set -= element
         assertEquals(otherElement, set.first())
@@ -333,8 +341,8 @@
         set += 1L
         set += 5L
         assertTrue(
-            "[1, 5]" == set.toString() ||
-                "[5, 1]" == set.toString()
+            "[${1L}, ${5L}]" == set.toString() ||
+                "[${5L}, ${1L}]" == set.toString()
         )
     }
 
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectFloatMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectFloatMapTest.kt
new file mode 100644
index 0000000..3ae3e6e
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectFloatMapTest.kt
@@ -0,0 +1,630 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+@Suppress("RemoveRedundantCallsOfConversionMethods")
+class ObjectFloatTest {
+    @Test
+    fun objectFloatMap() {
+        val map = MutableObjectFloatMap<String>()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun emptyObjectFloatMap() {
+        val map = emptyObjectFloatMap<String>()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyObjectFloatMap<String>(), map)
+    }
+
+    @Test
+    fun objectFloatMapFunction() {
+        val map = mutableObjectFloatMapOf<String>()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutableObjectFloatMap<String>(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun objectFloatMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutableObjectFloatMap<String>(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun objectFloatMapPairsFunction() {
+        val map = mutableObjectFloatMapOf(
+            "Hello" to 1f,
+            "Bonjour" to 2f
+        )
+        assertEquals(2, map.size)
+        assertEquals(1f, map["Hello"])
+        assertEquals(2f, map["Bonjour"])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutableObjectFloatMap<String>()
+        map["Hello"] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map["Hello"])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutableObjectFloatMap<String>(12)
+        map["Hello"] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map["Hello"])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutableObjectFloatMap<String>(2)
+        map["Hello"] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals(1f, map["Hello"])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutableObjectFloatMap<String>(0)
+        map["Hello"] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map["Hello"])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutableObjectFloatMap<String>()
+        map["Hello"] = 1f
+        map["Hello"] = 2f
+
+        assertEquals(1, map.size)
+        assertEquals(2f, map["Hello"])
+    }
+
+    @Test
+    fun put() {
+        val map = MutableObjectFloatMap<String>()
+
+        map.put("Hello", 1f)
+        assertEquals(1f, map["Hello"])
+        map.put("Hello", 2f)
+        assertEquals(2f, map["Hello"])
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutableObjectFloatMap<String?>()
+        map["Hello"] = 1f
+        map[null] = 2f
+        map["Bonjour"] = 2f
+
+        map.putAll(arrayOf("Hallo" to 3f, "Hola" to 7f))
+
+        assertEquals(5, map.size)
+        assertEquals(3f, map["Hallo"])
+        assertEquals(7f, map["Hola"])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutableObjectFloatMap<String>()
+        map += "Hello" to 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map["Hello"])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutableObjectFloatMap<String>()
+        map += arrayOf("Hallo" to 3f, "Hola" to 7f)
+
+        assertEquals(2, map.size)
+        assertEquals(3f, map["Hallo"])
+        assertEquals(7f, map["Hola"])
+    }
+
+    @Test
+    fun nullKey() {
+        val map = MutableObjectFloatMap<String?>()
+        map[null] = 1f
+
+        assertEquals(1, map.size)
+        assertEquals(1f, map[null])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutableObjectFloatMap<String>()
+        map["Hello"] = 1f
+
+        assertFailsWith<NoSuchElementException> {
+            map["Bonjour"]
+        }
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutableObjectFloatMap<String>()
+        map["Hello"] = 1f
+
+        assertEquals(2f, map.getOrDefault("Bonjour", 2f))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutableObjectFloatMap<String>()
+        map["Hello"] = 1f
+
+        assertEquals(3f, map.getOrElse("Hallo") { 3f })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutableObjectFloatMap<String>()
+        map["Hello"] = 1f
+
+        var counter = 0
+        map.getOrPut("Hello") {
+            counter++
+            2f
+        }
+        assertEquals(1f, map["Hello"])
+        assertEquals(0, counter)
+
+        map.getOrPut("Bonjour") {
+            counter++
+            2f
+        }
+        assertEquals(2f, map["Bonjour"])
+        assertEquals(1, counter)
+
+        map.getOrPut("Bonjour") {
+            counter++
+            3f
+        }
+        assertEquals(2f, map["Bonjour"])
+        assertEquals(1, counter)
+
+        map.getOrPut("Hallo") {
+            counter++
+            3f
+        }
+        assertEquals(3f, map["Hallo"])
+        assertEquals(2, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutableObjectFloatMap<String?>()
+        map.remove("Hello")
+
+        map["Hello"] = 1f
+        map.remove("Hello")
+        assertEquals(0, map.size)
+
+        map[null] = 1f
+        map.remove(null)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutableObjectFloatMap<String>(6)
+        map["Hello"] = 1f
+        map["Bonjour"] = 2f
+        map["Hallo"] = 3f
+        map["Konnichiwa"] = 4f
+        map["Ciao"] = 5f
+        map["Annyeong"] = 6f
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove("Hello")
+        map.remove("Bonjour")
+        map.remove("Hallo")
+        map.remove("Konnichiwa")
+        map.remove("Ciao")
+        map.remove("Annyeong")
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map["Hola"] = 7f
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutableObjectFloatMap<String>()
+        map["Hello"] = 1f
+        map["Bonjour"] = 2f
+        map["Hallo"] = 3f
+        map["Konnichiwa"] = 4f
+        map["Ciao"] = 5f
+        map["Annyeong"] = 6f
+
+        map.removeIf { key, _ -> key.startsWith('H') }
+
+        assertEquals(4, map.size)
+        assertEquals(2f, map["Bonjour"])
+        assertEquals(4f, map["Konnichiwa"])
+        assertEquals(5f, map["Ciao"])
+        assertEquals(6f, map["Annyeong"])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutableObjectFloatMap<String>()
+        map["Hello"] = 1f
+        map["Bonjour"] = 2f
+        map["Hallo"] = 3f
+
+        map -= "Hello"
+
+        assertEquals(2, map.size)
+        assertFalse("Hello" in map)
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutableObjectFloatMap<String>()
+        map["Hello"] = 1f
+        map["Bonjour"] = 2f
+        map["Hallo"] = 3f
+
+        map -= arrayOf("Hallo", "Bonjour")
+
+        assertEquals(1, map.size)
+        assertFalse("Hallo" in map)
+        assertFalse("Bonjour" in map)
+    }
+
+    @Test
+    fun minusIterable() {
+        val map = MutableObjectFloatMap<String>()
+        map["Hello"] = 1f
+        map["Bonjour"] = 2f
+        map["Hallo"] = 3f
+
+        map -= listOf("Hallo", "Bonjour")
+
+        assertEquals(1, map.size)
+        assertFalse("Hallo" in map)
+        assertFalse("Bonjour" in map)
+    }
+
+    @Test
+    fun minusSequence() {
+        val map = MutableObjectFloatMap<String>()
+        map["Hello"] = 1f
+        map["Bonjour"] = 2f
+        map["Hallo"] = 3f
+
+        map -= listOf("Hallo", "Bonjour").asSequence()
+
+        assertEquals(1, map.size)
+        assertFalse("Hallo" in map)
+        assertFalse("Bonjour" in map)
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutableObjectFloatMap<String?>()
+        assertFalse(map.remove("Hello", 1f))
+
+        map["Hello"] = 1f
+        assertTrue(map.remove("Hello", 1f))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutableObjectFloatMap<String>()
+
+        for (i in 0 until 1700) {
+            map[i.toString()] = i.toFloat()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutableObjectFloatMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toString()] = j.toFloat()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key, value.toInt().toString())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutableObjectFloatMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toString()] = j.toFloat()
+            }
+
+            var counter = 0
+            map.forEachKey { key ->
+                assertNotNull(key.toIntOrNull())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutableObjectFloatMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toString()] = j.toFloat()
+            }
+
+            var counter = 0
+            val values = BooleanArray(map.size)
+            map.forEachValue { value ->
+                values[value.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            values.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutableObjectFloatMap<String>()
+
+        for (i in 0 until 32) {
+            map[i.toString()] = i.toFloat()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutableObjectFloatMap<String?>()
+        assertEquals("{}", map.toString())
+
+        map["Hello"] = 1f
+        map["Bonjour"] = 2f
+        val oneString = 1f.toString()
+        val twoString = 2f.toString()
+        assertTrue(
+            "{Hello=$oneString, Bonjour=$twoString}" == map.toString() ||
+                "{Bonjour=$twoString, Hello=$oneString}" == map.toString()
+        )
+
+        map.clear()
+        map[null] = 2f
+        assertEquals("{null=$twoString}", map.toString())
+
+        val selfAsKeyMap = MutableObjectFloatMap<Any>()
+        selfAsKeyMap[selfAsKeyMap] = 1f
+        assertEquals("{(this)=$oneString}", selfAsKeyMap.toString())
+    }
+
+    @Test
+    fun equals() {
+        val map = MutableObjectFloatMap<String?>()
+        map["Hello"] = 1f
+        map[null] = 2f
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutableObjectFloatMap<String?>()
+        map2[null] = 2f
+
+        assertNotEquals(map, map2)
+
+        map2["Hello"] = 1f
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutableObjectFloatMap<String?>()
+        map["Hello"] = 1f
+        map[null] = 2f
+
+        assertTrue(map.containsKey("Hello"))
+        assertTrue(map.containsKey(null))
+        assertFalse(map.containsKey("Bonjour"))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutableObjectFloatMap<String?>()
+        map["Hello"] = 1f
+        map[null] = 2f
+
+        assertTrue("Hello" in map)
+        assertTrue(null in map)
+        assertFalse("Bonjour" in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutableObjectFloatMap<String?>()
+        map["Hello"] = 1f
+        map[null] = 2f
+
+        assertTrue(map.containsValue(1f))
+        assertTrue(map.containsValue(2f))
+        assertFalse(map.containsValue(3f))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutableObjectFloatMap<String?>()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map["Hello"] = 1f
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutableObjectFloatMap<String>()
+        assertEquals(0, map.count())
+
+        map["Hello"] = 1f
+        assertEquals(1, map.count())
+
+        map["Bonjour"] = 2f
+        map["Hallo"] = 3f
+        map["Konnichiwa"] = 4f
+        map["Ciao"] = 5f
+        map["Annyeong"] = 6f
+
+        assertEquals(2, map.count { key, _ -> key.startsWith("H") })
+        assertEquals(0, map.count { key, _ -> key.startsWith("W") })
+    }
+
+    @Test
+    fun any() {
+        val map = MutableObjectFloatMap<String>()
+        map["Hello"] = 1f
+        map["Bonjour"] = 2f
+        map["Hallo"] = 3f
+        map["Konnichiwa"] = 4f
+        map["Ciao"] = 5f
+        map["Annyeong"] = 6f
+
+        assertTrue(map.any { key, _ -> key.startsWith("K") })
+        assertFalse(map.any { key, _ -> key.startsWith("W") })
+    }
+
+    @Test
+    fun all() {
+        val map = MutableObjectFloatMap<String>()
+        map["Hello"] = 1f
+        map["Bonjour"] = 2f
+        map["Hallo"] = 3f
+        map["Konnichiwa"] = 4f
+        map["Ciao"] = 5f
+        map["Annyeong"] = 6f
+
+        assertTrue(map.all { key, value -> key.length >= 4 && value.toInt() >= 1 })
+        assertFalse(map.all { key, _ -> key.startsWith("W") })
+    }
+
+    @Test
+    fun trim() {
+        val map = MutableObjectFloatMap<String>()
+        assertEquals(7, map.trim())
+
+        map["Hello"] = 1f
+        map["Hallo"] = 3f
+
+        assertEquals(0, map.trim())
+
+        for (i in 0 until 1700) {
+            map[i.toString()] = i.toFloat()
+        }
+
+        assertEquals(2047, map.capacity)
+
+        // After removing these items, our capacity needs should go
+        // from 2047 down to 1023
+        for (i in 0 until 1700) {
+            if (i and 0x1 == 0x0) {
+                val s = i.toString()
+                map.remove(s)
+            }
+        }
+
+        assertEquals(1024, map.trim())
+        assertEquals(0, map.trim())
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectIntMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectIntMapTest.kt
new file mode 100644
index 0000000..d3bf7c7
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectIntMapTest.kt
@@ -0,0 +1,630 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+@Suppress("RemoveRedundantCallsOfConversionMethods")
+class ObjectIntTest {
+    @Test
+    fun objectIntMap() {
+        val map = MutableObjectIntMap<String>()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun emptyObjectIntMap() {
+        val map = emptyObjectIntMap<String>()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyObjectIntMap<String>(), map)
+    }
+
+    @Test
+    fun objectIntMapFunction() {
+        val map = mutableObjectIntMapOf<String>()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutableObjectIntMap<String>(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun objectIntMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutableObjectIntMap<String>(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun objectIntMapPairsFunction() {
+        val map = mutableObjectIntMapOf(
+            "Hello" to 1,
+            "Bonjour" to 2
+        )
+        assertEquals(2, map.size)
+        assertEquals(1, map["Hello"])
+        assertEquals(2, map["Bonjour"])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutableObjectIntMap<String>()
+        map["Hello"] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map["Hello"])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutableObjectIntMap<String>(12)
+        map["Hello"] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map["Hello"])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutableObjectIntMap<String>(2)
+        map["Hello"] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals(1, map["Hello"])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutableObjectIntMap<String>(0)
+        map["Hello"] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map["Hello"])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutableObjectIntMap<String>()
+        map["Hello"] = 1
+        map["Hello"] = 2
+
+        assertEquals(1, map.size)
+        assertEquals(2, map["Hello"])
+    }
+
+    @Test
+    fun put() {
+        val map = MutableObjectIntMap<String>()
+
+        map.put("Hello", 1)
+        assertEquals(1, map["Hello"])
+        map.put("Hello", 2)
+        assertEquals(2, map["Hello"])
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutableObjectIntMap<String?>()
+        map["Hello"] = 1
+        map[null] = 2
+        map["Bonjour"] = 2
+
+        map.putAll(arrayOf("Hallo" to 3, "Hola" to 7))
+
+        assertEquals(5, map.size)
+        assertEquals(3, map["Hallo"])
+        assertEquals(7, map["Hola"])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutableObjectIntMap<String>()
+        map += "Hello" to 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map["Hello"])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutableObjectIntMap<String>()
+        map += arrayOf("Hallo" to 3, "Hola" to 7)
+
+        assertEquals(2, map.size)
+        assertEquals(3, map["Hallo"])
+        assertEquals(7, map["Hola"])
+    }
+
+    @Test
+    fun nullKey() {
+        val map = MutableObjectIntMap<String?>()
+        map[null] = 1
+
+        assertEquals(1, map.size)
+        assertEquals(1, map[null])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutableObjectIntMap<String>()
+        map["Hello"] = 1
+
+        assertFailsWith<NoSuchElementException> {
+            map["Bonjour"]
+        }
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutableObjectIntMap<String>()
+        map["Hello"] = 1
+
+        assertEquals(2, map.getOrDefault("Bonjour", 2))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutableObjectIntMap<String>()
+        map["Hello"] = 1
+
+        assertEquals(3, map.getOrElse("Hallo") { 3 })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutableObjectIntMap<String>()
+        map["Hello"] = 1
+
+        var counter = 0
+        map.getOrPut("Hello") {
+            counter++
+            2
+        }
+        assertEquals(1, map["Hello"])
+        assertEquals(0, counter)
+
+        map.getOrPut("Bonjour") {
+            counter++
+            2
+        }
+        assertEquals(2, map["Bonjour"])
+        assertEquals(1, counter)
+
+        map.getOrPut("Bonjour") {
+            counter++
+            3
+        }
+        assertEquals(2, map["Bonjour"])
+        assertEquals(1, counter)
+
+        map.getOrPut("Hallo") {
+            counter++
+            3
+        }
+        assertEquals(3, map["Hallo"])
+        assertEquals(2, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutableObjectIntMap<String?>()
+        map.remove("Hello")
+
+        map["Hello"] = 1
+        map.remove("Hello")
+        assertEquals(0, map.size)
+
+        map[null] = 1
+        map.remove(null)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutableObjectIntMap<String>(6)
+        map["Hello"] = 1
+        map["Bonjour"] = 2
+        map["Hallo"] = 3
+        map["Konnichiwa"] = 4
+        map["Ciao"] = 5
+        map["Annyeong"] = 6
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove("Hello")
+        map.remove("Bonjour")
+        map.remove("Hallo")
+        map.remove("Konnichiwa")
+        map.remove("Ciao")
+        map.remove("Annyeong")
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map["Hola"] = 7
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutableObjectIntMap<String>()
+        map["Hello"] = 1
+        map["Bonjour"] = 2
+        map["Hallo"] = 3
+        map["Konnichiwa"] = 4
+        map["Ciao"] = 5
+        map["Annyeong"] = 6
+
+        map.removeIf { key, _ -> key.startsWith('H') }
+
+        assertEquals(4, map.size)
+        assertEquals(2, map["Bonjour"])
+        assertEquals(4, map["Konnichiwa"])
+        assertEquals(5, map["Ciao"])
+        assertEquals(6, map["Annyeong"])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutableObjectIntMap<String>()
+        map["Hello"] = 1
+        map["Bonjour"] = 2
+        map["Hallo"] = 3
+
+        map -= "Hello"
+
+        assertEquals(2, map.size)
+        assertFalse("Hello" in map)
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutableObjectIntMap<String>()
+        map["Hello"] = 1
+        map["Bonjour"] = 2
+        map["Hallo"] = 3
+
+        map -= arrayOf("Hallo", "Bonjour")
+
+        assertEquals(1, map.size)
+        assertFalse("Hallo" in map)
+        assertFalse("Bonjour" in map)
+    }
+
+    @Test
+    fun minusIterable() {
+        val map = MutableObjectIntMap<String>()
+        map["Hello"] = 1
+        map["Bonjour"] = 2
+        map["Hallo"] = 3
+
+        map -= listOf("Hallo", "Bonjour")
+
+        assertEquals(1, map.size)
+        assertFalse("Hallo" in map)
+        assertFalse("Bonjour" in map)
+    }
+
+    @Test
+    fun minusSequence() {
+        val map = MutableObjectIntMap<String>()
+        map["Hello"] = 1
+        map["Bonjour"] = 2
+        map["Hallo"] = 3
+
+        map -= listOf("Hallo", "Bonjour").asSequence()
+
+        assertEquals(1, map.size)
+        assertFalse("Hallo" in map)
+        assertFalse("Bonjour" in map)
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutableObjectIntMap<String?>()
+        assertFalse(map.remove("Hello", 1))
+
+        map["Hello"] = 1
+        assertTrue(map.remove("Hello", 1))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutableObjectIntMap<String>()
+
+        for (i in 0 until 1700) {
+            map[i.toString()] = i.toInt()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutableObjectIntMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toString()] = j.toInt()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key, value.toInt().toString())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutableObjectIntMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toString()] = j.toInt()
+            }
+
+            var counter = 0
+            map.forEachKey { key ->
+                assertNotNull(key.toIntOrNull())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutableObjectIntMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toString()] = j.toInt()
+            }
+
+            var counter = 0
+            val values = BooleanArray(map.size)
+            map.forEachValue { value ->
+                values[value.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            values.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutableObjectIntMap<String>()
+
+        for (i in 0 until 32) {
+            map[i.toString()] = i.toInt()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutableObjectIntMap<String?>()
+        assertEquals("{}", map.toString())
+
+        map["Hello"] = 1
+        map["Bonjour"] = 2
+        val oneString = 1.toString()
+        val twoString = 2.toString()
+        assertTrue(
+            "{Hello=$oneString, Bonjour=$twoString}" == map.toString() ||
+                "{Bonjour=$twoString, Hello=$oneString}" == map.toString()
+        )
+
+        map.clear()
+        map[null] = 2
+        assertEquals("{null=$twoString}", map.toString())
+
+        val selfAsKeyMap = MutableObjectIntMap<Any>()
+        selfAsKeyMap[selfAsKeyMap] = 1
+        assertEquals("{(this)=$oneString}", selfAsKeyMap.toString())
+    }
+
+    @Test
+    fun equals() {
+        val map = MutableObjectIntMap<String?>()
+        map["Hello"] = 1
+        map[null] = 2
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutableObjectIntMap<String?>()
+        map2[null] = 2
+
+        assertNotEquals(map, map2)
+
+        map2["Hello"] = 1
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutableObjectIntMap<String?>()
+        map["Hello"] = 1
+        map[null] = 2
+
+        assertTrue(map.containsKey("Hello"))
+        assertTrue(map.containsKey(null))
+        assertFalse(map.containsKey("Bonjour"))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutableObjectIntMap<String?>()
+        map["Hello"] = 1
+        map[null] = 2
+
+        assertTrue("Hello" in map)
+        assertTrue(null in map)
+        assertFalse("Bonjour" in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutableObjectIntMap<String?>()
+        map["Hello"] = 1
+        map[null] = 2
+
+        assertTrue(map.containsValue(1))
+        assertTrue(map.containsValue(2))
+        assertFalse(map.containsValue(3))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutableObjectIntMap<String?>()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map["Hello"] = 1
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutableObjectIntMap<String>()
+        assertEquals(0, map.count())
+
+        map["Hello"] = 1
+        assertEquals(1, map.count())
+
+        map["Bonjour"] = 2
+        map["Hallo"] = 3
+        map["Konnichiwa"] = 4
+        map["Ciao"] = 5
+        map["Annyeong"] = 6
+
+        assertEquals(2, map.count { key, _ -> key.startsWith("H") })
+        assertEquals(0, map.count { key, _ -> key.startsWith("W") })
+    }
+
+    @Test
+    fun any() {
+        val map = MutableObjectIntMap<String>()
+        map["Hello"] = 1
+        map["Bonjour"] = 2
+        map["Hallo"] = 3
+        map["Konnichiwa"] = 4
+        map["Ciao"] = 5
+        map["Annyeong"] = 6
+
+        assertTrue(map.any { key, _ -> key.startsWith("K") })
+        assertFalse(map.any { key, _ -> key.startsWith("W") })
+    }
+
+    @Test
+    fun all() {
+        val map = MutableObjectIntMap<String>()
+        map["Hello"] = 1
+        map["Bonjour"] = 2
+        map["Hallo"] = 3
+        map["Konnichiwa"] = 4
+        map["Ciao"] = 5
+        map["Annyeong"] = 6
+
+        assertTrue(map.all { key, value -> key.length >= 4 && value.toInt() >= 1 })
+        assertFalse(map.all { key, _ -> key.startsWith("W") })
+    }
+
+    @Test
+    fun trim() {
+        val map = MutableObjectIntMap<String>()
+        assertEquals(7, map.trim())
+
+        map["Hello"] = 1
+        map["Hallo"] = 3
+
+        assertEquals(0, map.trim())
+
+        for (i in 0 until 1700) {
+            map[i.toString()] = i.toInt()
+        }
+
+        assertEquals(2047, map.capacity)
+
+        // After removing these items, our capacity needs should go
+        // from 2047 down to 1023
+        for (i in 0 until 1700) {
+            if (i and 0x1 == 0x0) {
+                val s = i.toString()
+                map.remove(s)
+            }
+        }
+
+        assertEquals(1024, map.trim())
+        assertEquals(0, map.trim())
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectListTest.kt
new file mode 100644
index 0000000..d524dcf
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectListTest.kt
@@ -0,0 +1,1312 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+class ObjectListTest {
+    private val list: MutableObjectList<Int> = mutableObjectListOf(1, 2, 3, 4, 5)
+
+    @Test
+    fun emptyConstruction() {
+        val l = mutableObjectListOf<Int>()
+        assertEquals(0, l.size)
+        assertEquals(16, l.capacity)
+    }
+
+    @Test
+    fun sizeConstruction() {
+        val l = MutableObjectList<Int>(4)
+        assertEquals(4, l.capacity)
+    }
+
+    @Test
+    fun contentConstruction() {
+        val l = mutableObjectListOf(1, 2, 3)
+        assertEquals(3, l.size)
+        assertEquals(1, l[0])
+        assertEquals(2, l[1])
+        assertEquals(3, l[2])
+        assertEquals(3, l.capacity)
+        repeat(2) {
+            val l2 = mutableObjectListOf(1, 2, 3, 4, 5)
+            assertEquals(list, l2)
+            l2.removeAt(0)
+        }
+    }
+
+    @Test
+    fun hashCodeTest() {
+        val l2 = mutableObjectListOf(1, 2, 3, 4, 5)
+        assertEquals(list.hashCode(), l2.hashCode())
+        l2.removeAt(4)
+        assertNotEquals(list.hashCode(), l2.hashCode())
+        l2.add(5)
+        assertEquals(list.hashCode(), l2.hashCode())
+        l2.clear()
+        assertNotEquals(list.hashCode(), l2.hashCode())
+    }
+
+    @Test
+    fun equalsTest() {
+        val l2 = mutableObjectListOf(1, 2, 3, 4, 5)
+        assertEquals(list, l2)
+        assertNotEquals(list, mutableObjectListOf())
+        l2.removeAt(4)
+        assertNotEquals(list, l2)
+        l2.add(5)
+        assertEquals(list, l2)
+        l2.clear()
+        assertNotEquals(list, l2)
+    }
+
+    @Test
+    fun string() {
+        assertEquals("[1, 2, 3, 4, 5]", list.toString())
+        assertEquals("[]", mutableObjectListOf<Int>().toString())
+    }
+
+    @Test
+    fun size() {
+        assertEquals(5, list.size)
+        assertEquals(5, list.count())
+        val l2 = mutableObjectListOf<Int>()
+        assertEquals(0, l2.size)
+        assertEquals(0, l2.count())
+        l2 += 1
+        assertEquals(1, l2.size)
+        assertEquals(1, l2.count())
+    }
+
+    @Test
+    fun get() {
+        assertEquals(1, list[0])
+        assertEquals(5, list[4])
+        assertEquals(1, list.elementAt(0))
+        assertEquals(5, list.elementAt(4))
+    }
+
+    @Test
+    fun getOutOfBounds() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            list[5]
+        }
+    }
+
+    @Test
+    fun getOutOfBoundsNegative() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            list[-1]
+        }
+    }
+
+    @Test
+    fun elementAtOfBounds() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            list.elementAt(5)
+        }
+    }
+
+    @Test
+    fun elementAtOfBoundsNegative() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            list.elementAt(-1)
+        }
+    }
+
+    @Test
+    fun elementAtOrElse() {
+        assertEquals(1, list.elementAtOrElse(0) {
+            assertEquals(0, it)
+            0
+        })
+        assertEquals(0, list.elementAtOrElse(-1) {
+            assertEquals(-1, it)
+            0
+        })
+        assertEquals(0, list.elementAtOrElse(5) {
+            assertEquals(5, it)
+            0
+        })
+    }
+
+    @Test
+    fun count() {
+        assertEquals(1, list.count { it < 2 })
+        assertEquals(0, list.count { it < 0 })
+        assertEquals(5, list.count { it < 10 })
+    }
+
+    @Test
+    fun isEmpty() {
+        assertFalse(list.isEmpty())
+        assertFalse(list.none())
+        assertTrue(mutableObjectListOf<Int>().isEmpty())
+        assertTrue(mutableObjectListOf<Int>().none())
+    }
+
+    @Test
+    fun isNotEmpty() {
+        assertTrue(list.isNotEmpty())
+        assertTrue(list.any())
+        assertFalse(mutableObjectListOf<Int>().isNotEmpty())
+    }
+
+    @Test
+    fun indices() {
+        assertEquals(IntRange(0, 4), list.indices)
+        assertEquals(IntRange(0, -1), mutableObjectListOf<Int>().indices)
+    }
+
+    @Test
+    fun any() {
+        assertTrue(list.any { it == 5 })
+        assertTrue(list.any { it == 1 })
+        assertFalse(list.any { it == 0 })
+    }
+
+    @Test
+    fun reversedAny() {
+        val reversedList = mutableObjectListOf<Int>()
+        assertFalse(
+            list.reversedAny {
+                reversedList.add(it)
+                false
+            }
+        )
+        val reversedContent = mutableObjectListOf(5, 4, 3, 2, 1)
+        assertEquals(reversedContent, reversedList)
+
+        val reversedSublist = mutableObjectListOf<Int>()
+        assertTrue(
+            list.reversedAny {
+                reversedSublist.add(it)
+                reversedSublist.size == 2
+            }
+        )
+        assertEquals(reversedSublist, mutableObjectListOf(5, 4))
+    }
+
+    @Test
+    fun forEach() {
+        val copy = mutableObjectListOf<Int>()
+        list.forEach { copy += it }
+        assertEquals(list, copy)
+    }
+
+    @Test
+    fun forEachReversed() {
+        val copy = mutableObjectListOf<Int>()
+        list.forEachReversed { copy += it }
+        assertEquals(copy, mutableObjectListOf(5, 4, 3, 2, 1))
+    }
+
+    @Test
+    fun forEachIndexed() {
+        val copy = mutableObjectListOf<Int>()
+        val indices = mutableObjectListOf<Int>()
+        list.forEachIndexed { index, open ->
+            copy += open
+            indices += index
+        }
+        assertEquals(list, copy)
+        assertEquals(indices, mutableObjectListOf(0, 1, 2, 3, 4))
+    }
+
+    @Test
+    fun forEachReversedIndexed() {
+        val copy = mutableObjectListOf<Int>()
+        val indices = mutableObjectListOf<Int>()
+        list.forEachReversedIndexed { index, open ->
+            copy += open
+            indices += index
+        }
+        assertEquals(copy, mutableObjectListOf(5, 4, 3, 2, 1))
+        assertEquals(indices, mutableObjectListOf(4, 3, 2, 1, 0))
+    }
+
+    @Test
+    fun indexOfFirst() {
+        assertEquals(0, list.indexOfFirst { it < 2 })
+        assertEquals(4, list.indexOfFirst { it > 4 })
+        assertEquals(-1, list.indexOfFirst { it < 0 })
+        assertEquals(0, mutableObjectListOf(8, 8).indexOfFirst { it > 7 })
+    }
+
+    @Test
+    fun firstOrNullNoParam() {
+        assertEquals(1, list.firstOrNull())
+        assertNull(emptyObjectList<Int>().firstOrNull())
+    }
+
+    @Test
+    fun firstOrNull() {
+        assertEquals(1, list.firstOrNull { it < 5 })
+        assertEquals(3, list.firstOrNull { it > 2 })
+        assertEquals(5, list.firstOrNull { it > 4 })
+        assertNull(list.firstOrNull { it > 5 })
+    }
+
+    @Test
+    fun lastOrNullNoParam() {
+        assertEquals(5, list.lastOrNull())
+        assertNull(emptyObjectList<Int>().lastOrNull())
+    }
+
+    @Test
+    fun lastOrNull() {
+        assertEquals(4, list.lastOrNull { it < 5 })
+        assertEquals(5, list.lastOrNull { it > 2 })
+        assertEquals(1, list.lastOrNull { it < 2 })
+        assertNull(list.firstOrNull { it > 5 })
+    }
+
+    @Test
+    fun indexOfLast() {
+        assertEquals(0, list.indexOfLast { it < 2 })
+        assertEquals(4, list.indexOfLast { it > 4 })
+        assertEquals(-1, list.indexOfLast { it < 0 })
+        assertEquals(1, objectListOf(8, 8).indexOfLast { it > 7 })
+    }
+
+    @Test
+    fun contains() {
+        assertTrue(list.contains(5))
+        assertTrue(list.contains(1))
+        assertFalse(list.contains(0))
+    }
+
+    @Test
+    fun containsAllList() {
+        assertTrue(list.containsAll(mutableObjectListOf(2, 3, 1)))
+        assertFalse(list.containsAll(mutableObjectListOf(2, 3, 6)))
+    }
+
+    @Test
+    fun lastIndexOf() {
+        assertEquals(4, list.lastIndexOf(5))
+        assertEquals(1, list.lastIndexOf(2))
+        val copy = mutableObjectListOf<Int>()
+        copy.addAll(list)
+        copy.addAll(list)
+        assertEquals(5, copy.lastIndexOf(1))
+    }
+
+    @Test
+    fun first() {
+        assertEquals(1, list.first())
+    }
+
+    @Test
+    fun firstException() {
+        assertFailsWith(NoSuchElementException::class) {
+            mutableObjectListOf<Int>().first()
+        }
+    }
+
+    @Test
+    fun firstWithPredicate() {
+        assertEquals(5, list.first { it > 4 })
+        assertEquals(1, mutableObjectListOf(1, 5).first { it > 0 })
+    }
+
+    @Test
+    fun firstWithPredicateException() {
+        assertFailsWith(NoSuchElementException::class) {
+            mutableObjectListOf<Int>().first { it > 8 }
+        }
+    }
+
+    @Test
+    fun last() {
+        assertEquals(5, list.last())
+    }
+
+    @Test
+    fun lastException() {
+        assertFailsWith(NoSuchElementException::class) {
+            mutableObjectListOf<Int>().last()
+        }
+    }
+
+    @Test
+    fun lastWithPredicate() {
+        assertEquals(1, list.last { it < 2 })
+        assertEquals(5, objectListOf(1, 5).last { it > 0 })
+    }
+
+    @Test
+    fun lastWithPredicateException() {
+        assertFailsWith<NoSuchElementException> {
+            objectListOf(2).last { it > 2 }
+        }
+    }
+
+    @Test
+    fun fold() {
+        assertEquals("12345", list.fold("") { acc, i -> acc + i.toString() })
+    }
+
+    @Test
+    fun foldIndexed() {
+        assertEquals(
+            "01-12-23-34-45-",
+            list.foldIndexed("") { index, acc, i ->
+                "$acc$index$i-"
+            }
+        )
+    }
+
+    @Test
+    fun foldRight() {
+        assertEquals("54321", list.foldRight("") { i, acc -> acc + i.toString() })
+    }
+
+    @Test
+    fun foldRightIndexed() {
+        assertEquals(
+            "45-34-23-12-01-",
+            list.foldRightIndexed("") { index, i, acc ->
+                "$acc$index$i-"
+            }
+        )
+    }
+
+    @Test
+    fun add() {
+        val l = mutableObjectListOf(1, 2, 3)
+        l += 4
+        l.add(5)
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun addAtIndex() {
+        val l = mutableObjectListOf(2, 4)
+        l.add(2, 5)
+        l.add(0, 1)
+        l.add(2, 3)
+        assertEquals(list, l)
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l.add(-1, 2)
+        }
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l.add(6, 2)
+        }
+    }
+
+    @Test
+    fun addAllListAtIndex() {
+        val l = mutableObjectListOf(4)
+        val l2 = mutableObjectListOf(1, 2)
+        val l3 = mutableObjectListOf(5)
+        val l4 = mutableObjectListOf(3)
+        assertTrue(l4.addAll(1, l3))
+        assertTrue(l4.addAll(0, l2))
+        assertTrue(l4.addAll(3, l))
+        assertFalse(l4.addAll(0, mutableObjectListOf()))
+        assertEquals(list, l4)
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l4.addAll(6, mutableObjectListOf())
+        }
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l4.addAll(-1, mutableObjectListOf())
+        }
+    }
+
+    @Test
+    fun addAllObjectList() {
+        val l = MutableObjectList<Int>()
+        l.add(3)
+        l.add(4)
+        l.add(5)
+        val l2 = mutableObjectListOf(1, 2)
+        assertTrue(l2.addAll(l))
+        assertEquals(list, l2)
+        assertFalse(l2.addAll(mutableObjectListOf()))
+    }
+
+    @Test
+    fun addAllList() {
+        val l = listOf(3, 4, 5)
+        val l2 = mutableObjectListOf(1, 2)
+        assertTrue(l2.addAll(l))
+        assertEquals(list, l2)
+        assertFalse(l2.addAll(mutableObjectListOf()))
+    }
+
+    @Test
+    fun addAllIterable() {
+        val l = listOf(3, 4, 5) as Iterable<Int>
+        val l2 = mutableObjectListOf(1, 2)
+        assertTrue(l2.addAll(l))
+        assertEquals(list, l2)
+        assertFalse(l2.addAll(mutableObjectListOf()))
+    }
+
+    @Test
+    fun addAllSequence() {
+        val l = listOf(3, 4, 5).asSequence()
+        val l2 = mutableObjectListOf(1, 2)
+        assertTrue(l2.addAll(l))
+        assertEquals(list, l2)
+        assertFalse(l2.addAll(mutableObjectListOf()))
+    }
+
+    @Test
+    fun plusAssignObjectList() {
+        val l = objectListOf(3, 4, 5)
+        val l2 = mutableObjectListOf(1, 2)
+        l2 += l
+        assertEquals(list, l2)
+    }
+
+    @Test
+    fun plusAssignIterable() {
+        val l = listOf(3, 4, 5) as Iterable<Int>
+        val l2 = mutableObjectListOf(1, 2)
+        l2 += l
+        assertEquals(list, l2)
+    }
+
+    @Test
+    fun plusAssignSequence() {
+        val l = arrayOf(3, 4, 5).asSequence()
+        val l2 = mutableObjectListOf(1, 2)
+        l2 += l
+        assertEquals(list, l2)
+    }
+
+    @Test
+    fun addAllArrayAtIndex() {
+        val a1 = arrayOf(4)
+        val a2 = arrayOf(1, 2)
+        val a3 = arrayOf(5)
+        val l = mutableObjectListOf(3)
+        assertTrue(l.addAll(1, a3))
+        assertTrue(l.addAll(0, a2))
+        assertTrue(l.addAll(3, a1))
+        assertFalse(l.addAll(0, arrayOf()))
+        assertEquals(list, l)
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l.addAll(6, arrayOf())
+        }
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l.addAll(-1, arrayOf())
+        }
+    }
+
+    @Test
+    fun addAllArray() {
+        val a = arrayOf(3, 4, 5)
+        val v = mutableObjectListOf(1, 2)
+        v.addAll(a)
+        assertEquals(5, v.size)
+        assertEquals(3, v[2])
+        assertEquals(4, v[3])
+        assertEquals(5, v[4])
+    }
+
+    @Test
+    fun plusAssignArray() {
+        val a = arrayOf(3, 4, 5)
+        val v = mutableObjectListOf(1, 2)
+        v += a
+        assertEquals(list, v)
+    }
+
+    @Test
+    fun clear() {
+        val l = mutableObjectListOf<Int>()
+        l.addAll(list)
+        assertTrue(l.isNotEmpty())
+        l.clear()
+        assertTrue(l.isEmpty())
+        repeat(5) { index ->
+            assertNull(l.content[index])
+        }
+    }
+
+    @Test
+    fun trim() {
+        val l = mutableObjectListOf(1)
+        l.trim()
+        assertEquals(1, l.capacity)
+        l += arrayOf(1, 2, 3, 4, 5)
+        l.trim()
+        assertEquals(6, l.capacity)
+        assertEquals(6, l.size)
+        l.clear()
+        l.trim()
+        assertEquals(0, l.capacity)
+        l.trim(100)
+        assertEquals(0, l.capacity)
+        l += arrayOf(1, 2, 3, 4, 5)
+        l -= 5
+        l.trim(5)
+        assertEquals(5, l.capacity)
+        l.trim(4)
+        assertEquals(4, l.capacity)
+        l.trim(3)
+        assertEquals(4, l.capacity)
+    }
+
+    @Test
+    fun remove() {
+        val l = mutableObjectListOf(1, 2, 3, 4, 5)
+        l.remove(3)
+        assertEquals(mutableObjectListOf(1, 2, 4, 5), l)
+    }
+
+    @Test
+    fun removeIf() {
+        val l = mutableObjectListOf(1, 2, 3, 4, 5, 6)
+        l.removeIf { it == 100 }
+        assertEquals(objectListOf(1, 2, 3, 4, 5, 6), l)
+        l.removeIf { it % 2 == 0 }
+        assertEquals(objectListOf(1, 3, 5), l)
+        repeat(3) {
+            assertNull(l.content[3 + it])
+        }
+        l.removeIf { it != 3 }
+        assertEquals(objectListOf(3), l)
+    }
+
+    @Test
+    fun removeAt() {
+        val l = mutableObjectListOf(1, 2, 3, 4, 5)
+        l.removeAt(2)
+        assertNull(l.content[4])
+        assertEquals(mutableObjectListOf(1, 2, 4, 5), l)
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l.removeAt(6)
+        }
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l.removeAt(-1)
+        }
+    }
+
+    @Test
+    fun set() {
+        val l = mutableObjectListOf(0, 0, 0, 0, 0)
+        l[0] = 1
+        l[4] = 5
+        l[2] = 3
+        l[1] = 2
+        l[3] = 4
+        assertEquals(list, l)
+        assertFailsWith<IndexOutOfBoundsException> {
+            l[-1] = 1
+        }
+        assertFailsWith<IndexOutOfBoundsException> {
+            l[6] = 1
+        }
+        assertEquals(4, l.set(3, 1));
+    }
+
+    @Test
+    fun ensureCapacity() {
+        val l = mutableObjectListOf(1)
+        assertEquals(1, l.capacity)
+        l.ensureCapacity(5)
+        assertEquals(5, l.capacity)
+    }
+
+    @Test
+    fun removeAllObjectList() {
+        assertFalse(list.removeAll(mutableObjectListOf(0, 10, 15)))
+        val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+        assertTrue(l.removeAll(mutableObjectListOf(20, 0, 15, 10, 5)))
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun removeAllScatterSet() {
+        assertFalse(list.removeAll(scatterSetOf(0, 10, 15)))
+        val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+        assertTrue(l.removeAll(scatterSetOf(20, 0, 15, 10, 5)))
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun removeAllArray() {
+        assertFalse(list.removeAll(arrayOf(0, 10, 15)))
+        val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+        assertTrue(l.removeAll(arrayOf(20, 0, 15, 10, 5)))
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun removeAllList() {
+        assertFalse(list.removeAll(listOf(0, 10, 15)))
+        val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+        assertTrue(l.removeAll(listOf(20, 0, 15, 10, 5)))
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun removeAllIterable() {
+        assertFalse(list.removeAll(listOf(0, 10, 15) as Iterable<Int>))
+        val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+        assertTrue(l.removeAll(listOf(20, 0, 15, 10, 5) as Iterable<Int>))
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun removeAllSequence() {
+        assertFalse(list.removeAll(listOf(0, 10, 15).asSequence()))
+        val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+        assertTrue(l.removeAll(listOf(20, 0, 15, 10, 5).asSequence()))
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun minusAssignObjectList() {
+        val l = mutableObjectListOf<Int>().also { it += list }
+        l -= mutableObjectListOf(0, 10, 15)
+        assertEquals(list, l)
+        val l2 = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+        l2 -= mutableObjectListOf(20, 0, 15, 10, 5)
+        assertEquals(list, l2)
+    }
+
+    @Test
+    fun minusAssignScatterSet() {
+        val l = mutableObjectListOf<Int>().also { it += list }
+        l -= scatterSetOf(0, 10, 15)
+        assertEquals(list, l)
+        val l2 = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+        l2 -= scatterSetOf(20, 0, 15, 10, 5)
+        assertEquals(list, l2)
+    }
+
+    @Test
+    fun minusAssignArray() {
+        val l = mutableObjectListOf<Int>().also { it += list }
+        l -= arrayOf(0, 10, 15)
+        assertEquals(list, l)
+        val l2 = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+        l2 -= arrayOf(20, 0, 15, 10, 5)
+        assertEquals(list, l2)
+    }
+
+    @Test
+    fun minusAssignList() {
+        val l = mutableObjectListOf<Int>().also { it += list }
+        l -= listOf(0, 10, 15)
+        assertEquals(list, l)
+        val l2 = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+        l2 -= listOf(20, 0, 15, 10, 5)
+        assertEquals(list, l2)
+    }
+
+    @Test
+    fun minusAssignIterable() {
+        val l = mutableObjectListOf<Int>().also { it += list }
+        l -= listOf(0, 10, 15) as Iterable<Int>
+        assertEquals(list, l)
+        val l2 = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+        l2 -= listOf(20, 0, 15, 10, 5) as Iterable<Int>
+        assertEquals(list, l2)
+    }
+
+    @Test
+    fun minusAssignSequence() {
+        val l = mutableObjectListOf<Int>().also { it += list }
+        l -= listOf(0, 10, 15).asSequence()
+        assertEquals(list, l)
+        val l2 = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20, 5)
+        l2 -= listOf(20, 0, 15, 10, 5).asSequence()
+        assertEquals(list, l2)
+    }
+
+    @Test
+    fun retainAll() {
+        assertFalse(list.retainAll(mutableObjectListOf(1, 2, 3, 4, 5, 6)))
+        val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20)
+        assertTrue(l.retainAll(mutableObjectListOf(1, 2, 3, 4, 5, 6)))
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun retainAllArray() {
+        assertFalse(list.retainAll(arrayOf(1, 2, 3, 4, 5, 6)))
+        val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20)
+        assertTrue(l.retainAll(arrayOf(1, 2, 3, 4, 5, 6)))
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun retainAllCollection() {
+        assertFalse(list.retainAll(listOf(1, 2, 3, 4, 5, 6)))
+        val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20)
+        assertTrue(l.retainAll(listOf(1, 2, 3, 4, 5, 6)))
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun retainAllIterable() {
+        assertFalse(list.retainAll(listOf(1, 2, 3, 4, 5, 6) as Iterable<Int>))
+        val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20)
+        assertTrue(l.retainAll(listOf(1, 2, 3, 4, 5, 6) as Iterable<Int>))
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun retainAllSequence() {
+        assertFalse(list.retainAll(arrayOf(1, 2, 3, 4, 5, 6).asSequence()))
+        val l = mutableObjectListOf(0, 1, 15, 10, 2, 3, 4, 5, 20)
+        assertTrue(l.retainAll(arrayOf(1, 2, 3, 4, 5, 6).asSequence()))
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun removeRange() {
+        val l = mutableObjectListOf(1, 9, 7, 6, 2, 3, 4, 5)
+        l.removeRange(1, 4)
+        assertNull(l.content[5])
+        assertNull(l.content[6])
+        assertNull(l.content[7])
+        assertEquals(list, l)
+        assertFailsWith<IndexOutOfBoundsException> {
+            l.removeRange(6, 6)
+        }
+        assertFailsWith<IndexOutOfBoundsException> {
+            l.removeRange(100, 200)
+        }
+        assertFailsWith<IndexOutOfBoundsException> {
+            l.removeRange(-1, 0)
+        }
+        assertFailsWith<IllegalArgumentException> {
+            l.removeRange(3, 2)
+        }
+    }
+
+    @Test
+    fun testEmptyObjectList() {
+        val l = emptyObjectList<Int>()
+        assertEquals(0, l.size)
+    }
+
+    @Test
+    fun objectListOfEmpty() {
+        val l = objectListOf<Int>()
+        assertEquals(0, l.size)
+    }
+
+    @Test
+    fun objectListOfOneValue() {
+        val l = objectListOf(2)
+        assertEquals(1, l.size)
+        assertEquals(2, l[0])
+    }
+
+    @Test
+    fun objectListOfTwoValues() {
+        val l = objectListOf(2, 1)
+        assertEquals(2, l.size)
+        assertEquals(2, l[0])
+        assertEquals(1, l[1])
+    }
+
+    @Test
+    fun objectListOfThreeValues() {
+        val l = objectListOf(2, 10, -1)
+        assertEquals(3, l.size)
+        assertEquals(2, l[0])
+        assertEquals(10, l[1])
+        assertEquals(-1, l[2])
+    }
+
+    @Test
+    fun objectListOfFourValues() {
+        val l = objectListOf(2, 10, -1, 10)
+        assertEquals(4, l.size)
+        assertEquals(2, l[0])
+        assertEquals(10, l[1])
+        assertEquals(-1, l[2])
+        assertEquals(10, l[3])
+    }
+
+    @Test
+    fun mutableObjectListOfOneValue() {
+        val l = mutableObjectListOf(2)
+        assertEquals(1, l.size)
+        assertEquals(1, l.capacity)
+        assertEquals(2, l[0])
+    }
+
+    @Test
+    fun mutableObjectListOfTwoValues() {
+        val l = mutableObjectListOf(2, 1)
+        assertEquals(2, l.size)
+        assertEquals(2, l.capacity)
+        assertEquals(2, l[0])
+        assertEquals(1, l[1])
+    }
+
+    @Test
+    fun mutableObjectListOfThreeValues() {
+        val l = mutableObjectListOf(2, 10, -1)
+        assertEquals(3, l.size)
+        assertEquals(3, l.capacity)
+        assertEquals(2, l[0])
+        assertEquals(10, l[1])
+        assertEquals(-1, l[2])
+    }
+
+    @Test
+    fun mutableObjectListOfFourValues() {
+        val l = mutableObjectListOf(2, 10, -1, 10)
+        assertEquals(4, l.size)
+        assertEquals(4, l.capacity)
+        assertEquals(2, l[0])
+        assertEquals(10, l[1])
+        assertEquals(-1, l[2])
+        assertEquals(10, l[3])
+    }
+
+    @Test
+    fun iterator() {
+        val l = mutableObjectListOf(1, 2, 3, 4, 5)
+        val iterator = l.asMutableList().iterator()
+        assertTrue(iterator.hasNext())
+        assertEquals(1, iterator.next())
+        assertTrue(iterator.hasNext())
+        assertEquals(2, iterator.next())
+        assertTrue(iterator.hasNext())
+        assertEquals(3, iterator.next())
+        assertTrue(iterator.hasNext())
+        iterator.remove()
+        assertTrue(iterator.hasNext())
+        assertEquals(l, mutableObjectListOf(1, 2, 4, 5))
+
+        assertEquals(4, iterator.next())
+        assertTrue(iterator.hasNext())
+        assertEquals(5, iterator.next())
+        assertFalse(iterator.hasNext())
+        iterator.remove()
+        assertEquals(l, mutableObjectListOf(1, 2, 4))
+    }
+
+    @Test
+    fun listIterator() {
+        val l = mutableObjectListOf(1, 2, 3, 4, 5)
+        val iterator = l.asMutableList().listIterator()
+        assertEquals(1, iterator.next())
+        assertEquals(1, iterator.previous())
+        assertEquals(0, iterator.nextIndex())
+        iterator.add(6)
+        assertEquals(1, iterator.nextIndex())
+        assertEquals(0, iterator.previousIndex())
+        assertEquals(6, iterator.previous())
+        assertEquals(l, mutableObjectListOf(6, 1, 2, 3, 4, 5))
+    }
+
+    @Test
+    fun listIteratorInitialIndex() {
+        val iterator = list.asMutableList().listIterator(2)
+        assertEquals(2, iterator.nextIndex())
+    }
+
+    @Test
+    fun subList() {
+        val l = list.asMutableList().subList(1, 4)
+        assertEquals(3, l.size)
+        assertEquals(2, l[0])
+        assertEquals(3, l[1])
+        assertEquals(4, l[2])
+    }
+
+    @Test
+    fun subListContains() {
+        val l = list.asMutableList().subList(1, 4)
+        assertTrue(l.contains(2))
+        assertTrue(l.contains(3))
+        assertTrue(l.contains(4))
+        assertFalse(l.contains(5))
+        assertFalse(l.contains(1))
+    }
+
+    @Test
+    fun subListContainsAll() {
+        val l = list.asMutableList().subList(1, 4)
+        val smallList = listOf(2, 3, 4)
+        assertTrue(l.containsAll(smallList))
+        val largeList = listOf(3, 4, 5)
+        assertFalse(l.containsAll(largeList))
+    }
+
+    @Test
+    fun subListIndexOf() {
+        val l = list.asMutableList().subList(1, 4)
+        assertEquals(0, l.indexOf(2))
+        assertEquals(2, l.indexOf(4))
+        assertEquals(-1, l.indexOf(1))
+        val l2 = mutableObjectListOf(2, 1, 1, 3).asMutableList().subList(1, 2)
+        assertEquals(0, l2.indexOf(1))
+    }
+
+    @Test
+    fun subListIsEmpty() {
+        val l = list.asMutableList().subList(1, 4)
+        assertFalse(l.isEmpty())
+        assertTrue(list.asMutableList().subList(4, 4).isEmpty())
+    }
+
+    @Test
+    fun subListIterator() {
+        val l = list.asMutableList().subList(1, 4)
+        val l2 = mutableListOf<Int>()
+        l.forEach { l2 += it }
+        assertEquals(3, l2.size)
+        assertEquals(2, l2[0])
+        assertEquals(3, l2[1])
+        assertEquals(4, l2[2])
+    }
+
+    @Test
+    fun subListLastIndexOf() {
+        val l = list.asMutableList().subList(1, 4)
+        assertEquals(0, l.lastIndexOf(2))
+        assertEquals(2, l.lastIndexOf(4))
+        assertEquals(-1, l.lastIndexOf(1))
+        val l2 = mutableObjectListOf(2, 1, 1, 3).asMutableList().subList(1, 3)
+        assertEquals(1, l2.lastIndexOf(1))
+    }
+
+    @Test
+    fun subListAdd() {
+        val v = mutableObjectListOf(1, 2, 3)
+        val l = v.asMutableList().subList(1, 2)
+        assertTrue(l.add(4))
+        assertEquals(2, l.size)
+        assertEquals(4, v.size)
+        assertEquals(2, l[0])
+        assertEquals(4, l[1])
+        assertEquals(2, v[1])
+        assertEquals(4, v[2])
+        assertEquals(3, v[3])
+    }
+
+    @Test
+    fun subListAddIndex() {
+        val v = mutableObjectListOf(6, 1, 2, 3)
+        val l = v.asMutableList().subList(1, 3)
+        l.add(1, 4)
+        assertEquals(3, l.size)
+        assertEquals(5, v.size)
+        assertEquals(1, l[0])
+        assertEquals(4, l[1])
+        assertEquals(2, l[2])
+        assertEquals(1, v[1])
+        assertEquals(4, v[2])
+        assertEquals(2, v[3])
+    }
+
+    @Test
+    fun subListAddAllAtIndex() {
+        val v = mutableObjectListOf(6, 1, 2, 3)
+        val l = v.asMutableList().subList(1, 3)
+        l.addAll(1, listOf(4, 5))
+        assertEquals(4, l.size)
+        assertEquals(6, v.size)
+        assertEquals(1, l[0])
+        assertEquals(4, l[1])
+        assertEquals(5, l[2])
+        assertEquals(2, l[3])
+        assertEquals(1, v[1])
+        assertEquals(4, v[2])
+        assertEquals(5, v[3])
+        assertEquals(2, v[4])
+    }
+
+    @Test
+    fun subListAddAll() {
+        val v = mutableObjectListOf(6, 1, 2, 3)
+        val l = v.asMutableList().subList(1, 3)
+        l.addAll(listOf(4, 5))
+        assertEquals(4, l.size)
+        assertEquals(6, v.size)
+        assertEquals(1, l[0])
+        assertEquals(2, l[1])
+        assertEquals(4, l[2])
+        assertEquals(5, l[3])
+        assertEquals(1, v[1])
+        assertEquals(2, v[2])
+        assertEquals(4, v[3])
+        assertEquals(5, v[4])
+        assertEquals(3, v[5])
+    }
+
+    @Test
+    fun subListClear() {
+        val v = mutableObjectListOf(1, 2, 3, 4, 5)
+        val l = v.asMutableList().subList(1, 4)
+        l.clear()
+        assertEquals(0, l.size)
+        assertEquals(2, v.size)
+        assertEquals(1, v[0])
+        assertEquals(5, v[1])
+        kotlin.test.assertNull(v.content[2])
+        kotlin.test.assertNull(v.content[3])
+        kotlin.test.assertNull(v.content[4])
+    }
+
+    @Test
+    fun subListListIterator() {
+        val l = list.asMutableList().subList(1, 4)
+        val listIterator = l.listIterator()
+        assertTrue(listIterator.hasNext())
+        assertFalse(listIterator.hasPrevious())
+        assertEquals(0, listIterator.nextIndex())
+        assertEquals(2, listIterator.next())
+    }
+
+    @Test
+    fun subListListIteratorWithIndex() {
+        val l = list.asMutableList().subList(1, 4)
+        val listIterator = l.listIterator(1)
+        assertTrue(listIterator.hasNext())
+        assertTrue(listIterator.hasPrevious())
+        assertEquals(1, listIterator.nextIndex())
+        assertEquals(3, listIterator.next())
+    }
+
+    @Test
+    fun subListRemove() {
+        val l = mutableObjectListOf(1, 2, 3, 4, 5)
+        val l2 = l.asMutableList().subList(1, 4)
+        assertTrue(l2.remove(3))
+        assertEquals(l, mutableObjectListOf(1, 2, 4, 5))
+        assertEquals(2, l2.size)
+        assertEquals(2, l2[0])
+        assertEquals(4, l2[1])
+        assertFalse(l2.remove(3))
+        assertEquals(l, mutableObjectListOf(1, 2, 4, 5))
+        assertEquals(2, l2.size)
+    }
+
+    @Test
+    fun subListRemoveAll() {
+        val l = mutableObjectListOf(1, 2, 3, 4, 5)
+        val l2 = l.asMutableList().subList(1, 4)
+        assertFalse(l2.removeAll(listOf(1, 5, -1)))
+        assertEquals(5, l.size)
+        assertEquals(3, l2.size)
+        assertTrue(l2.removeAll(listOf(3, 4, 5)))
+        assertEquals(3, l.size)
+        assertEquals(1, l2.size)
+    }
+
+    @Test
+    fun subListRemoveAt() {
+        val l = mutableObjectListOf(1, 2, 3, 4, 5)
+        val l2 = l.asMutableList().subList(1, 4)
+        assertEquals(3, l2.removeAt(1))
+        assertEquals(4, l.size)
+        assertEquals(2, l2.size)
+        assertEquals(4, l2.removeAt(1))
+        assertEquals(1, l2.size)
+    }
+
+    @Test
+    fun subListRetainAll() {
+        val l = mutableObjectListOf(1, 2, 3, 4, 5)
+        val l2 = l.asMutableList().subList(1, 4)
+        val l3 = objectListOf(1, 2, 3, 4, 5)
+        assertFalse(l2.retainAll(l3.asList()))
+        assertFalse(l2.retainAll(listOf(2, 3, 4)))
+        assertEquals(3, l2.size)
+        assertEquals(5, l.size)
+        assertTrue(l2.retainAll(setOf(1, 2, 4)))
+        assertEquals(4, l.size)
+        assertEquals(2, l2.size)
+        assertEquals(l, objectListOf(1, 2, 4, 5))
+    }
+
+    @Test
+    fun subListSet() {
+        val l = mutableObjectListOf(1, 2, 3, 4, 5)
+        val l2 = l.asMutableList().subList(1, 4)
+        l2[1] = 10
+        assertEquals(10, l2[1])
+        assertEquals(3, l2.size)
+        assertEquals(10, l[2])
+    }
+
+    @Test
+    fun subListSubList() {
+        val l = objectListOf(1, 2, 3, 4, 5).asList().subList(1, 5)
+        val l2 = l.subList(1, 3)
+        assertEquals(2, l2.size)
+        assertEquals(3, l2[0])
+    }
+
+    @Suppress("KotlinConstantConditions")
+    @Test
+    fun list_outOfBounds_Get_Below() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            val l = mutableObjectListOf(1, 2, 3, 4).asMutableList()
+            l[-1]
+        }
+    }
+
+    @Suppress("KotlinConstantConditions")
+    @Test
+    fun sublist_outOfBounds_Get_Below() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            val l = mutableObjectListOf(1, 2, 3, 4).asMutableList().subList(1, 2)
+            l[-1]
+        }
+    }
+
+    @Test
+    fun list_outOfBounds_Get_Above() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            val l = mutableObjectListOf(1, 2, 3, 4).asMutableList()
+            l[4]
+        }
+    }
+
+    @Test
+    fun sublist_outOfBounds_Get_Above() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            val l = mutableObjectListOf(1, 2, 3, 4).asMutableList().subList(1, 2)
+            l[1]
+        }
+    }
+
+    @Test
+    fun list_outOfBounds_RemoveAt_Below() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            val l = mutableObjectListOf(0, 1, 2, 3).asMutableList()
+            l.removeAt(-1)
+        }
+    }
+
+    @Test
+    fun sublist_outOfBounds_RemoveAt_Below() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            val l = mutableObjectListOf(0, 1, 2, 3).asMutableList().subList(1, 2)
+            l.removeAt(-1)
+        }
+    }
+
+    @Test
+    fun list_outOfBounds_RemoveAt_Above() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            val l = mutableObjectListOf(0, 1, 2, 3).asMutableList()
+            l.removeAt(4)
+        }
+    }
+
+    @Test
+    fun sublist_outOfBounds_RemoveAt_Above() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            val l = mutableObjectListOf(0, 1, 2, 3).asMutableList().subList(1, 2)
+            l.removeAt(1)
+        }
+    }
+
+    @Suppress("KotlinConstantConditions")
+    @Test
+    fun list_outOfBounds_Set_Below() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            val l = mutableObjectListOf(0, 1, 2, 3).asMutableList()
+            l[-1] = 1
+        }
+    }
+
+    @Suppress("KotlinConstantConditions")
+    @Test
+    fun sublist_outOfBounds_Set_Below() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            val l = mutableObjectListOf(0, 1, 2, 3).asMutableList().subList(1, 2)
+            l[-1] = 1
+        }
+    }
+
+    @Test
+    fun list_outOfBounds_Set_Above() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            val l = mutableObjectListOf(0, 1, 2, 3).asMutableList()
+            l[4] = 1
+        }
+    }
+
+    @Test
+    fun sublist_outOfBounds_Set_Above() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            val l = mutableObjectListOf(0, 1, 2, 3).asMutableList().subList(1, 2)
+            l[1] = 1
+        }
+    }
+
+    @Test
+    fun list_outOfBounds_SubList_Below() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            val l = mutableObjectListOf(0, 1, 2, 3).asMutableList()
+            l.subList(-1, 1)
+        }
+    }
+
+    @Test
+    fun sublist_outOfBounds_SubList_Below() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            val l = mutableObjectListOf(0, 1, 2, 3).asMutableList().subList(1, 2)
+            l.subList(-1, 1)
+        }
+    }
+
+    @Test
+    fun list_outOfBounds_SubList_Above() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            val l = mutableObjectListOf(0, 1, 2, 3).asMutableList()
+            l.subList(5, 5)
+        }
+    }
+
+    @Test
+    fun sublist_outOfBounds_SubList_Above() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            val l = mutableObjectListOf(0, 1, 2, 3).asMutableList().subList(1, 2)
+            l.subList(1, 2)
+        }
+    }
+
+    @Test
+    fun list_outOfBounds_SubList_Order() {
+        assertFailsWith(IllegalArgumentException::class) {
+            val l = mutableObjectListOf(0, 1, 2, 3).asMutableList()
+            l.subList(3, 2)
+        }
+    }
+
+    @Test
+    fun sublist_outOfBounds_SubList_Order() {
+        assertFailsWith(IllegalArgumentException::class) {
+            val l = mutableObjectListOf(0, 1, 2, 3).asMutableList().subList(1, 2)
+            l.subList(1, 0)
+        }
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectLongMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectLongMapTest.kt
new file mode 100644
index 0000000..3cf8c73
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectLongMapTest.kt
@@ -0,0 +1,630 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+@Suppress("RemoveRedundantCallsOfConversionMethods")
+class ObjectLongTest {
+    @Test
+    fun objectLongMap() {
+        val map = MutableObjectLongMap<String>()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun emptyObjectLongMap() {
+        val map = emptyObjectLongMap<String>()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyObjectLongMap<String>(), map)
+    }
+
+    @Test
+    fun objectLongMapFunction() {
+        val map = mutableObjectLongMapOf<String>()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutableObjectLongMap<String>(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun objectLongMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutableObjectLongMap<String>(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun objectLongMapPairsFunction() {
+        val map = mutableObjectLongMapOf(
+            "Hello" to 1L,
+            "Bonjour" to 2L
+        )
+        assertEquals(2, map.size)
+        assertEquals(1L, map["Hello"])
+        assertEquals(2L, map["Bonjour"])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutableObjectLongMap<String>()
+        map["Hello"] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map["Hello"])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutableObjectLongMap<String>(12)
+        map["Hello"] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map["Hello"])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutableObjectLongMap<String>(2)
+        map["Hello"] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals(1L, map["Hello"])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutableObjectLongMap<String>(0)
+        map["Hello"] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map["Hello"])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutableObjectLongMap<String>()
+        map["Hello"] = 1L
+        map["Hello"] = 2L
+
+        assertEquals(1, map.size)
+        assertEquals(2L, map["Hello"])
+    }
+
+    @Test
+    fun put() {
+        val map = MutableObjectLongMap<String>()
+
+        map.put("Hello", 1L)
+        assertEquals(1L, map["Hello"])
+        map.put("Hello", 2L)
+        assertEquals(2L, map["Hello"])
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutableObjectLongMap<String?>()
+        map["Hello"] = 1L
+        map[null] = 2L
+        map["Bonjour"] = 2L
+
+        map.putAll(arrayOf("Hallo" to 3L, "Hola" to 7L))
+
+        assertEquals(5, map.size)
+        assertEquals(3L, map["Hallo"])
+        assertEquals(7L, map["Hola"])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutableObjectLongMap<String>()
+        map += "Hello" to 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map["Hello"])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutableObjectLongMap<String>()
+        map += arrayOf("Hallo" to 3L, "Hola" to 7L)
+
+        assertEquals(2, map.size)
+        assertEquals(3L, map["Hallo"])
+        assertEquals(7L, map["Hola"])
+    }
+
+    @Test
+    fun nullKey() {
+        val map = MutableObjectLongMap<String?>()
+        map[null] = 1L
+
+        assertEquals(1, map.size)
+        assertEquals(1L, map[null])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutableObjectLongMap<String>()
+        map["Hello"] = 1L
+
+        assertFailsWith<NoSuchElementException> {
+            map["Bonjour"]
+        }
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutableObjectLongMap<String>()
+        map["Hello"] = 1L
+
+        assertEquals(2L, map.getOrDefault("Bonjour", 2L))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutableObjectLongMap<String>()
+        map["Hello"] = 1L
+
+        assertEquals(3L, map.getOrElse("Hallo") { 3L })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutableObjectLongMap<String>()
+        map["Hello"] = 1L
+
+        var counter = 0
+        map.getOrPut("Hello") {
+            counter++
+            2L
+        }
+        assertEquals(1L, map["Hello"])
+        assertEquals(0, counter)
+
+        map.getOrPut("Bonjour") {
+            counter++
+            2L
+        }
+        assertEquals(2L, map["Bonjour"])
+        assertEquals(1, counter)
+
+        map.getOrPut("Bonjour") {
+            counter++
+            3L
+        }
+        assertEquals(2L, map["Bonjour"])
+        assertEquals(1, counter)
+
+        map.getOrPut("Hallo") {
+            counter++
+            3L
+        }
+        assertEquals(3L, map["Hallo"])
+        assertEquals(2, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutableObjectLongMap<String?>()
+        map.remove("Hello")
+
+        map["Hello"] = 1L
+        map.remove("Hello")
+        assertEquals(0, map.size)
+
+        map[null] = 1L
+        map.remove(null)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutableObjectLongMap<String>(6)
+        map["Hello"] = 1L
+        map["Bonjour"] = 2L
+        map["Hallo"] = 3L
+        map["Konnichiwa"] = 4L
+        map["Ciao"] = 5L
+        map["Annyeong"] = 6L
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove("Hello")
+        map.remove("Bonjour")
+        map.remove("Hallo")
+        map.remove("Konnichiwa")
+        map.remove("Ciao")
+        map.remove("Annyeong")
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map["Hola"] = 7L
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutableObjectLongMap<String>()
+        map["Hello"] = 1L
+        map["Bonjour"] = 2L
+        map["Hallo"] = 3L
+        map["Konnichiwa"] = 4L
+        map["Ciao"] = 5L
+        map["Annyeong"] = 6L
+
+        map.removeIf { key, _ -> key.startsWith('H') }
+
+        assertEquals(4, map.size)
+        assertEquals(2L, map["Bonjour"])
+        assertEquals(4L, map["Konnichiwa"])
+        assertEquals(5L, map["Ciao"])
+        assertEquals(6L, map["Annyeong"])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutableObjectLongMap<String>()
+        map["Hello"] = 1L
+        map["Bonjour"] = 2L
+        map["Hallo"] = 3L
+
+        map -= "Hello"
+
+        assertEquals(2, map.size)
+        assertFalse("Hello" in map)
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutableObjectLongMap<String>()
+        map["Hello"] = 1L
+        map["Bonjour"] = 2L
+        map["Hallo"] = 3L
+
+        map -= arrayOf("Hallo", "Bonjour")
+
+        assertEquals(1, map.size)
+        assertFalse("Hallo" in map)
+        assertFalse("Bonjour" in map)
+    }
+
+    @Test
+    fun minusIterable() {
+        val map = MutableObjectLongMap<String>()
+        map["Hello"] = 1L
+        map["Bonjour"] = 2L
+        map["Hallo"] = 3L
+
+        map -= listOf("Hallo", "Bonjour")
+
+        assertEquals(1, map.size)
+        assertFalse("Hallo" in map)
+        assertFalse("Bonjour" in map)
+    }
+
+    @Test
+    fun minusSequence() {
+        val map = MutableObjectLongMap<String>()
+        map["Hello"] = 1L
+        map["Bonjour"] = 2L
+        map["Hallo"] = 3L
+
+        map -= listOf("Hallo", "Bonjour").asSequence()
+
+        assertEquals(1, map.size)
+        assertFalse("Hallo" in map)
+        assertFalse("Bonjour" in map)
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutableObjectLongMap<String?>()
+        assertFalse(map.remove("Hello", 1L))
+
+        map["Hello"] = 1L
+        assertTrue(map.remove("Hello", 1L))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutableObjectLongMap<String>()
+
+        for (i in 0 until 1700) {
+            map[i.toString()] = i.toLong()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutableObjectLongMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toString()] = j.toLong()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key, value.toInt().toString())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutableObjectLongMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toString()] = j.toLong()
+            }
+
+            var counter = 0
+            map.forEachKey { key ->
+                assertNotNull(key.toIntOrNull())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutableObjectLongMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toString()] = j.toLong()
+            }
+
+            var counter = 0
+            val values = BooleanArray(map.size)
+            map.forEachValue { value ->
+                values[value.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            values.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutableObjectLongMap<String>()
+
+        for (i in 0 until 32) {
+            map[i.toString()] = i.toLong()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutableObjectLongMap<String?>()
+        assertEquals("{}", map.toString())
+
+        map["Hello"] = 1L
+        map["Bonjour"] = 2L
+        val oneString = 1L.toString()
+        val twoString = 2L.toString()
+        assertTrue(
+            "{Hello=$oneString, Bonjour=$twoString}" == map.toString() ||
+                "{Bonjour=$twoString, Hello=$oneString}" == map.toString()
+        )
+
+        map.clear()
+        map[null] = 2L
+        assertEquals("{null=$twoString}", map.toString())
+
+        val selfAsKeyMap = MutableObjectLongMap<Any>()
+        selfAsKeyMap[selfAsKeyMap] = 1L
+        assertEquals("{(this)=$oneString}", selfAsKeyMap.toString())
+    }
+
+    @Test
+    fun equals() {
+        val map = MutableObjectLongMap<String?>()
+        map["Hello"] = 1L
+        map[null] = 2L
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutableObjectLongMap<String?>()
+        map2[null] = 2L
+
+        assertNotEquals(map, map2)
+
+        map2["Hello"] = 1L
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutableObjectLongMap<String?>()
+        map["Hello"] = 1L
+        map[null] = 2L
+
+        assertTrue(map.containsKey("Hello"))
+        assertTrue(map.containsKey(null))
+        assertFalse(map.containsKey("Bonjour"))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutableObjectLongMap<String?>()
+        map["Hello"] = 1L
+        map[null] = 2L
+
+        assertTrue("Hello" in map)
+        assertTrue(null in map)
+        assertFalse("Bonjour" in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutableObjectLongMap<String?>()
+        map["Hello"] = 1L
+        map[null] = 2L
+
+        assertTrue(map.containsValue(1L))
+        assertTrue(map.containsValue(2L))
+        assertFalse(map.containsValue(3L))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutableObjectLongMap<String?>()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map["Hello"] = 1L
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutableObjectLongMap<String>()
+        assertEquals(0, map.count())
+
+        map["Hello"] = 1L
+        assertEquals(1, map.count())
+
+        map["Bonjour"] = 2L
+        map["Hallo"] = 3L
+        map["Konnichiwa"] = 4L
+        map["Ciao"] = 5L
+        map["Annyeong"] = 6L
+
+        assertEquals(2, map.count { key, _ -> key.startsWith("H") })
+        assertEquals(0, map.count { key, _ -> key.startsWith("W") })
+    }
+
+    @Test
+    fun any() {
+        val map = MutableObjectLongMap<String>()
+        map["Hello"] = 1L
+        map["Bonjour"] = 2L
+        map["Hallo"] = 3L
+        map["Konnichiwa"] = 4L
+        map["Ciao"] = 5L
+        map["Annyeong"] = 6L
+
+        assertTrue(map.any { key, _ -> key.startsWith("K") })
+        assertFalse(map.any { key, _ -> key.startsWith("W") })
+    }
+
+    @Test
+    fun all() {
+        val map = MutableObjectLongMap<String>()
+        map["Hello"] = 1L
+        map["Bonjour"] = 2L
+        map["Hallo"] = 3L
+        map["Konnichiwa"] = 4L
+        map["Ciao"] = 5L
+        map["Annyeong"] = 6L
+
+        assertTrue(map.all { key, value -> key.length >= 4 && value.toInt() >= 1 })
+        assertFalse(map.all { key, _ -> key.startsWith("W") })
+    }
+
+    @Test
+    fun trim() {
+        val map = MutableObjectLongMap<String>()
+        assertEquals(7, map.trim())
+
+        map["Hello"] = 1L
+        map["Hallo"] = 3L
+
+        assertEquals(0, map.trim())
+
+        for (i in 0 until 1700) {
+            map[i.toString()] = i.toLong()
+        }
+
+        assertEquals(2047, map.capacity)
+
+        // After removing these items, our capacity needs should go
+        // from 2047 down to 1023
+        for (i in 0 until 1700) {
+            if (i and 0x1 == 0x0) {
+                val s = i.toString()
+                map.remove(s)
+            }
+        }
+
+        assertEquals(1024, map.trim())
+        assertEquals(0, map.trim())
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterMapTest.kt
index bdd3338..a869735 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterMapTest.kt
@@ -87,6 +87,13 @@
     }
 
     @Test
+    fun insertIndex0() {
+        val map = MutableScatterMap<Float, Long>()
+        map.put(1f, 100L)
+        assertEquals(100L, map[1f])
+    }
+
+    @Test
     fun addToSizedMap() {
         val map = MutableScatterMap<String, String>(12)
         map["Hello"] = "World"
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java b/collection/collection/src/commonTest/kotlin/androidx/collection/TestValueClass.kt
similarity index 65%
copy from camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
copy to collection/collection/src/commonTest/kotlin/androidx/collection/TestValueClass.kt
index 7a355f8..7580c60 100644
--- a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/TestValueClass.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,16 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package androidx.collection
 
-package androidx.camera.effects;
-
-import androidx.annotation.RestrictTo;
+import kotlin.jvm.JvmInline
 
 /**
- * Provides a portrait post-processing effect.
- *
+ * This is a value class to test ValueClassSet and ValueClassList
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class Portrait {
-    // TODO: implement this
+@JvmInline
+value class TestValueClass(val value: ULong) {
+    override fun toString(): String {
+        return ">$value<"
+    }
 }
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ValueClassListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ValueClassListTest.kt
new file mode 100644
index 0000000..9f7b4a9
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ValueClassListTest.kt
@@ -0,0 +1,856 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.collection
+
+import androidx.collection.template.MutableTestValueClassList
+import androidx.collection.template.emptyTestValueClassList
+import androidx.collection.template.mutableTestValueClassListOf
+import androidx.collection.template.testValueClassListOf
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertTrue
+
+// ValueClassList was created with:
+// sed -e "s/PACKAGE/androidx.collection.template/" -e "s/VALUE_CLASS/TestValueClass/g" \
+//     -e "s/vALUE_CLASS/testValueClass/g" -e "s/BACKING_PROPERTY/value.toLong()/g" \
+//     -e "s/TO_PARAM/.toULong()/g" -e "s/PRIMITIVE/Long/g" -e "s/VALUE_PKG/androidx.collection/" \
+//     collection/collection/template/ValueClassList.kt.template \
+//     > collection/collection/src/commonTest/kotlin/androidx/collection/template/TestValueClassList.kt
+
+class ValueClassListTest {
+    private val list: MutableTestValueClassList = mutableTestValueClassListOf().also {
+        it += TestValueClass(1UL)
+        it += TestValueClass(2UL)
+        it += TestValueClass(3UL)
+        it += TestValueClass(4UL)
+        it += TestValueClass(5UL)
+    }
+
+    @Test
+    fun emptyConstruction() {
+        val l = mutableTestValueClassListOf()
+        assertEquals(0, l.size)
+        assertEquals(16, l.capacity)
+    }
+
+    @Test
+    fun sizeConstruction() {
+        val l = MutableTestValueClassList(4)
+        assertEquals(4, l.capacity)
+    }
+
+    @Test
+    fun contentConstruction() {
+        val l = mutableTestValueClassListOf(
+            TestValueClass(1UL),
+            TestValueClass(2UL),
+            TestValueClass(3UL)
+        )
+        assertEquals(3, l.size)
+        assertEquals(TestValueClass(1UL), l[0])
+        assertEquals(TestValueClass(2UL), l[1])
+        assertEquals(TestValueClass(3UL), l[2])
+        assertEquals(3, l.capacity)
+        repeat(2) {
+            val l2 = mutableTestValueClassListOf().also {
+                it += TestValueClass(1UL)
+                it += TestValueClass(2UL)
+                it += TestValueClass(3UL)
+                it += TestValueClass(4UL)
+                it += TestValueClass(5UL)
+            }
+            assertEquals(list, l2)
+            l2.removeAt(0)
+        }
+    }
+
+    @Test
+    fun hashCodeTest() {
+        val l2 = mutableTestValueClassListOf().also {
+            it += TestValueClass(1UL)
+            it += TestValueClass(2UL)
+            it += TestValueClass(3UL)
+            it += TestValueClass(4UL)
+            it += TestValueClass(5UL)
+        }
+        assertEquals(list.hashCode(), l2.hashCode())
+        l2.removeAt(4)
+        assertNotEquals(list.hashCode(), l2.hashCode())
+        l2.add(TestValueClass(5UL))
+        assertEquals(list.hashCode(), l2.hashCode())
+        l2.clear()
+        assertNotEquals(list.hashCode(), l2.hashCode())
+    }
+
+    @Test
+    fun equalsTest() {
+        val l2 = mutableTestValueClassListOf().also {
+            it += TestValueClass(1UL)
+            it += TestValueClass(2UL)
+            it += TestValueClass(3UL)
+            it += TestValueClass(4UL)
+            it += TestValueClass(5UL)
+        }
+        assertEquals(list, l2)
+        assertNotEquals(list, mutableTestValueClassListOf())
+        l2.removeAt(4)
+        assertNotEquals(list, l2)
+        l2.add(TestValueClass(5UL))
+        assertEquals(list, l2)
+        l2.clear()
+        assertNotEquals(list, l2)
+    }
+
+    @Test
+    fun string() {
+        assertEquals("[>1<, >2<, >3<, >4<, >5<]", list.toString())
+        assertEquals("[]", mutableTestValueClassListOf().toString())
+    }
+
+    @Test
+    fun size() {
+        assertEquals(5, list.size)
+        assertEquals(5, list.count())
+        val l2 = mutableTestValueClassListOf()
+        assertEquals(0, l2.size)
+        assertEquals(0, l2.count())
+        l2 += TestValueClass(1UL)
+        assertEquals(1, l2.size)
+        assertEquals(1, l2.count())
+    }
+
+    @Test
+    fun get() {
+        assertEquals(TestValueClass(1UL), list[0])
+        assertEquals(TestValueClass(5UL), list[4])
+        assertEquals(TestValueClass(1UL), list.elementAt(0))
+        assertEquals(TestValueClass(5UL), list.elementAt(4))
+    }
+
+    @Test
+    fun getOutOfBounds() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            list[5]
+        }
+    }
+
+    @Test
+    fun getOutOfBoundsNegative() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            list[-1]
+        }
+    }
+
+    @Test
+    fun elementAtOfBounds() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            list.elementAt(5)
+        }
+    }
+
+    @Test
+    fun elementAtOfBoundsNegative() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            list.elementAt(-1)
+        }
+    }
+
+    @Test
+    fun elementAtOrElse() {
+        assertEquals(TestValueClass(1UL), list.elementAtOrElse(0) {
+            assertEquals(0, it)
+            TestValueClass(0UL)
+        })
+        assertEquals(TestValueClass(0UL), list.elementAtOrElse(-1) {
+            assertEquals(-1, it)
+            TestValueClass(0UL)
+        })
+        assertEquals(TestValueClass(0UL), list.elementAtOrElse(5) {
+            assertEquals(5, it)
+            TestValueClass(0UL)
+        })
+    }
+
+    @Test
+    fun count() {
+        assertEquals(1, list.count { it.value < 2UL })
+        assertEquals(0, list.count { it.value < 0UL })
+        assertEquals(5, list.count { it.value < 10UL })
+    }
+
+    @Test
+    fun isEmpty() {
+        assertFalse(list.isEmpty())
+        assertFalse(list.none())
+        assertTrue(mutableTestValueClassListOf().isEmpty())
+        assertTrue(mutableTestValueClassListOf().none())
+    }
+
+    @Test
+    fun isNotEmpty() {
+        assertTrue(list.isNotEmpty())
+        assertTrue(list.any())
+        assertFalse(mutableTestValueClassListOf().isNotEmpty())
+    }
+
+    @Test
+    fun indices() {
+        assertEquals(IntRange(0, 4), list.indices)
+        assertEquals(IntRange(0, -1), mutableTestValueClassListOf().indices)
+    }
+
+    @Test
+    fun any() {
+        assertTrue(list.any { it == TestValueClass(5UL) })
+        assertTrue(list.any { it == TestValueClass(1UL) })
+        assertFalse(list.any { it == TestValueClass(0UL) })
+    }
+
+    @Test
+    fun reversedAny() {
+        val reversedList = mutableTestValueClassListOf()
+        assertFalse(
+            list.reversedAny {
+                reversedList.add(it)
+                false
+            }
+        )
+        val reversedContent = mutableTestValueClassListOf().also {
+            it += TestValueClass(5UL)
+            it += TestValueClass(4UL)
+            it += TestValueClass(3UL)
+            it += TestValueClass(2UL)
+            it += TestValueClass(1UL)
+        }
+        assertEquals(reversedContent, reversedList)
+
+        val reversedSublist = mutableTestValueClassListOf()
+        assertTrue(
+            list.reversedAny {
+                reversedSublist.add(it)
+                reversedSublist.size == 2
+            }
+        )
+        assertEquals(
+            reversedSublist,
+            mutableTestValueClassListOf(TestValueClass(5UL), TestValueClass(4UL))
+        )
+    }
+
+    @Test
+    fun forEach() {
+        val copy = mutableTestValueClassListOf()
+        list.forEach { copy += it }
+        assertEquals(list, copy)
+    }
+
+    @Test
+    fun forEachReversed() {
+        val copy = mutableTestValueClassListOf()
+        list.forEachReversed { copy += it }
+        assertEquals(
+            copy,
+            mutableTestValueClassListOf().also {
+                it += TestValueClass(5UL)
+                it += TestValueClass(4UL)
+                it += TestValueClass(3UL)
+                it += TestValueClass(2UL)
+                it += TestValueClass(1UL)
+            }
+        )
+    }
+
+    @Test
+    fun forEachIndexed() {
+        val copy = mutableTestValueClassListOf()
+        val indices = mutableTestValueClassListOf()
+        list.forEachIndexed { index, item ->
+            copy += item
+            indices += TestValueClass(index.toULong())
+        }
+        assertEquals(list, copy)
+        assertEquals(
+            indices,
+            mutableTestValueClassListOf().also {
+                it += TestValueClass(0UL)
+                it += TestValueClass(1UL)
+                it += TestValueClass(2UL)
+                it += TestValueClass(3UL)
+                it += TestValueClass(4UL)
+            }
+        )
+    }
+
+    @Test
+    fun forEachReversedIndexed() {
+        val copy = mutableTestValueClassListOf()
+        val indices = mutableTestValueClassListOf()
+        list.forEachReversedIndexed { index, item ->
+            copy += item
+            indices += TestValueClass(index.toULong())
+        }
+        assertEquals(
+            copy,
+            mutableTestValueClassListOf().also {
+                it += TestValueClass(5UL)
+                it += TestValueClass(4UL)
+                it += TestValueClass(3UL)
+                it += TestValueClass(2UL)
+                it += TestValueClass(1UL)
+            })
+        assertEquals(
+            indices,
+            mutableTestValueClassListOf().also {
+                it += TestValueClass(4UL)
+                it += TestValueClass(3UL)
+                it += TestValueClass(2UL)
+                it += TestValueClass(1UL)
+                it += TestValueClass(0UL)
+            })
+    }
+
+    @Test
+    fun indexOfFirst() {
+        assertEquals(0, list.indexOfFirst { it == TestValueClass(1UL) })
+        assertEquals(4, list.indexOfFirst { it == TestValueClass(5UL) })
+        assertEquals(-1, list.indexOfFirst { it == TestValueClass(0UL) })
+        assertEquals(
+            0,
+            mutableTestValueClassListOf(
+                TestValueClass(8UL),
+                TestValueClass(8UL)
+            ).indexOfFirst { it == TestValueClass(8UL) })
+    }
+
+    @Test
+    fun indexOfLast() {
+        assertEquals(0, list.indexOfLast { it == TestValueClass(1UL) })
+        assertEquals(4, list.indexOfLast { it == TestValueClass(5UL) })
+        assertEquals(-1, list.indexOfLast { it == TestValueClass(0UL) })
+        assertEquals(
+            1,
+            mutableTestValueClassListOf(
+                TestValueClass(8UL),
+                TestValueClass(8UL)
+            ).indexOfLast { it == TestValueClass(8UL) })
+    }
+
+    @Test
+    fun contains() {
+        assertTrue(list.contains(TestValueClass(5UL)))
+        assertTrue(list.contains(TestValueClass(1UL)))
+        assertFalse(list.contains(TestValueClass(0UL)))
+    }
+
+    @Test
+    fun containsAllList() {
+        assertTrue(
+            list.containsAll(
+                mutableTestValueClassListOf(
+                    TestValueClass(2UL),
+                    TestValueClass(3UL),
+                    TestValueClass(1UL)
+                )
+            )
+        )
+        assertFalse(
+            list.containsAll(
+                mutableTestValueClassListOf(
+                    TestValueClass(2UL),
+                    TestValueClass(3UL),
+                    TestValueClass(6UL)
+                )
+            )
+        )
+    }
+
+    @Test
+    fun lastIndexOf() {
+        assertEquals(4, list.lastIndexOf(TestValueClass(5UL)))
+        assertEquals(1, list.lastIndexOf(TestValueClass(2UL)))
+        val copy = mutableTestValueClassListOf()
+        copy.addAll(list)
+        copy.addAll(list)
+        assertEquals(5, copy.lastIndexOf(TestValueClass(1UL)))
+    }
+
+    @Test
+    fun first() {
+        assertEquals(TestValueClass(1UL), list.first())
+    }
+
+    @Test
+    fun firstException() {
+        assertFailsWith(NoSuchElementException::class) {
+            mutableTestValueClassListOf().first()
+        }
+    }
+
+    @Test
+    fun firstWithPredicate() {
+        assertEquals(TestValueClass(5UL), list.first { it == TestValueClass(5UL) })
+        assertEquals(
+            TestValueClass(1UL),
+            mutableTestValueClassListOf(TestValueClass(1UL), TestValueClass(5UL)).first {
+                it != TestValueClass(0UL)
+            })
+    }
+
+    @Test
+    fun firstWithPredicateException() {
+        assertFailsWith(NoSuchElementException::class) {
+            mutableTestValueClassListOf().first { it == TestValueClass(8UL) }
+        }
+    }
+
+    @Test
+    fun last() {
+        assertEquals(TestValueClass(5UL), list.last())
+    }
+
+    @Test
+    fun lastException() {
+        assertFailsWith(NoSuchElementException::class) {
+            mutableTestValueClassListOf().last()
+        }
+    }
+
+    @Test
+    fun lastWithPredicate() {
+        assertEquals(TestValueClass(1UL), list.last { it == TestValueClass(1UL) })
+        assertEquals(
+            TestValueClass(5UL),
+            mutableTestValueClassListOf(TestValueClass(1UL), TestValueClass(5UL)).last {
+                it != TestValueClass(0UL)
+            })
+    }
+
+    @Test
+    fun lastWithPredicateException() {
+        assertFailsWith(NoSuchElementException::class) {
+            mutableTestValueClassListOf().last { it == TestValueClass(8UL) }
+        }
+    }
+
+    @Test
+    fun fold() {
+        assertEquals("12345", list.fold("") { acc, i -> acc + i.value.toString() })
+    }
+
+    @Test
+    fun foldIndexed() {
+        assertEquals(
+            "01-12-23-34-45-",
+            list.foldIndexed("") { index, acc, i ->
+                "$acc$index${i.value}-"
+            }
+        )
+    }
+
+    @Test
+    fun foldRight() {
+        assertEquals("54321", list.foldRight("") { i, acc -> acc + i.value.toString() })
+    }
+
+    @Test
+    fun foldRightIndexed() {
+        assertEquals(
+            "45-34-23-12-01-",
+            list.foldRightIndexed("") { index, i, acc ->
+                "$acc$index${i.value}-"
+            }
+        )
+    }
+
+    @Test
+    fun add() {
+        val l = mutableTestValueClassListOf(
+            TestValueClass(1UL),
+            TestValueClass(2UL),
+            TestValueClass(3UL)
+        )
+        l += TestValueClass(4UL)
+        l.add(TestValueClass(5UL))
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun addAtIndex() {
+        val l = mutableTestValueClassListOf(TestValueClass(2UL), TestValueClass(4UL))
+        l.add(2, TestValueClass(5UL))
+        l.add(0, TestValueClass(1UL))
+        l.add(2, TestValueClass(3UL))
+        assertEquals(list, l)
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l.add(-1, TestValueClass(2UL))
+        }
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l.add(6, TestValueClass(2UL))
+        }
+    }
+
+    @Test
+    fun addAllListAtIndex() {
+        val l = mutableTestValueClassListOf(TestValueClass(4UL))
+        val l2 = mutableTestValueClassListOf(TestValueClass(1UL), TestValueClass(2UL))
+        val l3 = mutableTestValueClassListOf(TestValueClass(5UL))
+        val l4 = mutableTestValueClassListOf(TestValueClass(3UL))
+        assertTrue(l4.addAll(1, l3))
+        assertTrue(l4.addAll(0, l2))
+        assertTrue(l4.addAll(3, l))
+        assertFalse(l4.addAll(0, mutableTestValueClassListOf()))
+        assertEquals(list, l4)
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l4.addAll(6, mutableTestValueClassListOf())
+        }
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l4.addAll(-1, mutableTestValueClassListOf())
+        }
+    }
+
+    @Test
+    fun addAllList() {
+        val l = MutableTestValueClassList()
+        l.add(TestValueClass(3UL))
+        l.add(TestValueClass(4UL))
+        l.add(TestValueClass(5UL))
+        val l2 = mutableTestValueClassListOf(TestValueClass(1UL), TestValueClass(2UL))
+        assertTrue(l2.addAll(l))
+        assertEquals(list, l2)
+        assertFalse(l2.addAll(mutableTestValueClassListOf()))
+    }
+
+    @Test
+    fun plusAssignList() {
+        val l = MutableTestValueClassList()
+        l.add(TestValueClass(3UL))
+        l.add(TestValueClass(4UL))
+        l.add(TestValueClass(5UL))
+        val l2 = mutableTestValueClassListOf(TestValueClass(1UL), TestValueClass(2UL))
+        l2 += l
+        assertEquals(list, l2)
+    }
+
+    @Test
+    fun clear() {
+        val l = mutableTestValueClassListOf()
+        l.addAll(list)
+        assertTrue(l.isNotEmpty())
+        l.clear()
+        assertTrue(l.isEmpty())
+    }
+
+    @Test
+    fun trim() {
+        val l = mutableTestValueClassListOf(TestValueClass(1UL))
+        l.trim()
+        assertEquals(1, l.capacity)
+        l += TestValueClass(1UL)
+        l += TestValueClass(2UL)
+        l += TestValueClass(3UL)
+        l += TestValueClass(4UL)
+        l += TestValueClass(5UL)
+        l.trim()
+        assertEquals(6, l.capacity)
+        assertEquals(6, l.size)
+        l.clear()
+        l.trim()
+        assertEquals(0, l.capacity)
+        l.trim(100)
+        assertEquals(0, l.capacity)
+        l += TestValueClass(1UL)
+        l += TestValueClass(2UL)
+        l += TestValueClass(3UL)
+        l += TestValueClass(4UL)
+        l += TestValueClass(5UL)
+        l -= TestValueClass(5UL)
+        l.trim(5)
+        assertEquals(5, l.capacity)
+        l.trim(4)
+        assertEquals(4, l.capacity)
+        l.trim(3)
+        assertEquals(4, l.capacity)
+    }
+
+    @Test
+    fun remove() {
+        val l = mutableTestValueClassListOf()
+        l += list
+        l.remove(TestValueClass(3UL))
+        assertEquals(
+            mutableTestValueClassListOf().also {
+                it += TestValueClass(1UL)
+                it += TestValueClass(2UL)
+                it += TestValueClass(4UL)
+                it += TestValueClass(5UL)
+            },
+            l
+        )
+    }
+
+    @Test
+    fun removeAt() {
+        val l = mutableTestValueClassListOf()
+        l += list
+        l.removeAt(2)
+        assertEquals(
+            mutableTestValueClassListOf().also {
+                it += TestValueClass(1UL)
+                it += TestValueClass(2UL)
+                it += TestValueClass(4UL)
+                it += TestValueClass(5UL)
+            },
+            l
+        )
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l.removeAt(6)
+        }
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l.removeAt(-1)
+        }
+    }
+
+    @Test
+    fun set() {
+        val l = mutableTestValueClassListOf()
+        repeat(5) { l += TestValueClass(0UL) }
+        l[0] = TestValueClass(1UL)
+        l[4] = TestValueClass(5UL)
+        l[2] = TestValueClass(3UL)
+        l[1] = TestValueClass(2UL)
+        l[3] = TestValueClass(4UL)
+        assertEquals(list, l)
+        assertFailsWith<IndexOutOfBoundsException> {
+            l.set(-1, TestValueClass(1UL))
+        }
+        assertFailsWith<IndexOutOfBoundsException> {
+            l.set(6, TestValueClass(1UL))
+        }
+        assertEquals(TestValueClass(4UL), l.set(3, TestValueClass(1UL)));
+    }
+
+    @Test
+    fun ensureCapacity() {
+        val l = mutableTestValueClassListOf(TestValueClass(1UL))
+        assertEquals(1, l.capacity)
+        l.ensureCapacity(5)
+        assertEquals(5, l.capacity)
+    }
+
+    @Test
+    fun removeAllList() {
+        assertFalse(
+            list.removeAll(
+                mutableTestValueClassListOf(
+                    TestValueClass(0UL),
+                    TestValueClass(10UL),
+                    TestValueClass(15UL)
+                )
+            )
+        )
+        val l = mutableTestValueClassListOf()
+
+        l += TestValueClass(0UL)
+        l += TestValueClass(1UL)
+        l += TestValueClass(15UL)
+        l += TestValueClass(10UL)
+        l += TestValueClass(2UL)
+        l += TestValueClass(3UL)
+        l += TestValueClass(4UL)
+        l += TestValueClass(5UL)
+        l += TestValueClass(20UL)
+        l += TestValueClass(5UL)
+        assertTrue(
+            l.removeAll(
+                mutableTestValueClassListOf().also {
+                    it += TestValueClass(20UL)
+                    it += TestValueClass(0UL)
+                    it += TestValueClass(15UL)
+                    it += TestValueClass(10UL)
+                    it += TestValueClass(5UL)
+                }
+            )
+        )
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun minusAssignList() {
+        val l = mutableTestValueClassListOf().also { it += list }
+        l -= mutableTestValueClassListOf(
+            TestValueClass(0UL),
+            TestValueClass(10UL),
+            TestValueClass(15UL)
+        )
+        assertEquals(list, l)
+        val l2 = mutableTestValueClassListOf()
+
+        l2 += TestValueClass(0UL)
+        l2 += TestValueClass(1UL)
+        l2 += TestValueClass(15UL)
+        l2 += TestValueClass(10UL)
+        l2 += TestValueClass(2UL)
+        l2 += TestValueClass(3UL)
+        l2 += TestValueClass(4UL)
+        l2 += TestValueClass(5UL)
+        l2 += TestValueClass(20UL)
+        l2 += TestValueClass(5UL)
+        l2 -= mutableTestValueClassListOf().also {
+            it += TestValueClass(20UL)
+            it += TestValueClass(0UL)
+            it += TestValueClass(15UL)
+            it += TestValueClass(10UL)
+            it += TestValueClass(5UL)
+        }
+        assertEquals(list, l2)
+    }
+
+    @Test
+    fun retainAll() {
+        assertFalse(
+            list.retainAll(
+                mutableTestValueClassListOf().also {
+                    it += TestValueClass(1UL)
+                    it += TestValueClass(2UL)
+                    it += TestValueClass(3UL)
+                    it += TestValueClass(4UL)
+                    it += TestValueClass(5UL)
+                    it += TestValueClass(6UL)
+                }
+            )
+        )
+        val l = mutableTestValueClassListOf()
+            l += TestValueClass(0UL)
+            l += TestValueClass(1UL)
+            l += TestValueClass(15UL)
+            l += TestValueClass(10UL)
+            l += TestValueClass(2UL)
+            l += TestValueClass(3UL)
+            l += TestValueClass(4UL)
+            l += TestValueClass(5UL)
+            l += TestValueClass(20UL)
+        assertTrue(
+            l.retainAll(
+                mutableTestValueClassListOf().also {
+                    it += TestValueClass(1UL)
+                    it += TestValueClass(2UL)
+                    it += TestValueClass(3UL)
+                    it += TestValueClass(4UL)
+                    it += TestValueClass(5UL)
+                    it += TestValueClass(6UL)
+                }
+            )
+        )
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun removeRange() {
+        val l = mutableTestValueClassListOf()
+        l += TestValueClass(1UL)
+        l += TestValueClass(9UL)
+        l += TestValueClass(7UL)
+        l += TestValueClass(6UL)
+        l += TestValueClass(2UL)
+        l += TestValueClass(3UL)
+        l += TestValueClass(4UL)
+        l += TestValueClass(5UL)
+        l.removeRange(1, 4)
+        assertEquals(list, l)
+        assertFailsWith<IndexOutOfBoundsException> {
+            l.removeRange(6, 6)
+        }
+        assertFailsWith<IndexOutOfBoundsException> {
+            l.removeRange(100, 200)
+        }
+        assertFailsWith<IndexOutOfBoundsException> {
+            l.removeRange(-1, 0)
+        }
+        assertFailsWith<IllegalArgumentException> {
+            l.removeRange(3, 2)
+        }
+    }
+
+    @Test
+    fun testEmptyTestValueClassList() {
+        val l = emptyTestValueClassList()
+        assertEquals(0, l.size)
+    }
+
+    @Test
+    fun testValueClassListOfEmpty() {
+        val l = testValueClassListOf()
+        assertEquals(0, l.size)
+    }
+
+    @Test
+    fun testValueClassListOfOneValue() {
+        val l = testValueClassListOf(TestValueClass(2UL))
+        assertEquals(1, l.size)
+        assertEquals(TestValueClass(2UL), l[0])
+    }
+
+    @Test
+    fun testValueClassListOfTwoValues() {
+        val l = testValueClassListOf(TestValueClass(2UL), TestValueClass(1UL))
+        assertEquals(2, l.size)
+        assertEquals(TestValueClass(2UL), l[0])
+        assertEquals(TestValueClass(1UL), l[1])
+    }
+
+    @Test
+    fun testValueClassListOfThreeValues() {
+        val l = testValueClassListOf(TestValueClass(2UL), TestValueClass(10UL), TestValueClass(1UL))
+        assertEquals(3, l.size)
+        assertEquals(TestValueClass(2UL), l[0])
+        assertEquals(TestValueClass(10UL), l[1])
+        assertEquals(TestValueClass(1UL), l[2])
+    }
+
+    @Test
+    fun mutableTestValueClassListOfOneValue() {
+        val l = mutableTestValueClassListOf(TestValueClass(2UL))
+        assertEquals(1, l.size)
+        assertEquals(1, l.capacity)
+        assertEquals(TestValueClass(2UL), l[0])
+    }
+
+    @Test
+    fun mutableTestValueClassListOfTwoValues() {
+        val l = mutableTestValueClassListOf(TestValueClass(2UL), TestValueClass(1UL))
+        assertEquals(2, l.size)
+        assertEquals(2, l.capacity)
+        assertEquals(TestValueClass(2UL), l[0])
+        assertEquals(TestValueClass(1UL), l[1])
+    }
+
+    @Test
+    fun mutableTestValueClassListOfThreeValues() {
+        val l = mutableTestValueClassListOf(
+            TestValueClass(2UL),
+            TestValueClass(10UL),
+            TestValueClass(1UL)
+        )
+        assertEquals(3, l.size)
+        assertEquals(3, l.capacity)
+        assertEquals(TestValueClass(2UL), l[0])
+        assertEquals(TestValueClass(10UL), l[1])
+        assertEquals(TestValueClass(1UL), l[2])
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ValueClassSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ValueClassSetTest.kt
new file mode 100644
index 0000000..efe9085
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ValueClassSetTest.kt
@@ -0,0 +1,535 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.collection
+
+import androidx.collection.template.MutableTestValueClassSet
+import androidx.collection.template.TestValueClassSet
+import androidx.collection.template.emptyTestValueClassSet
+import androidx.collection.template.mutableTestValueClassSetOf
+import androidx.collection.template.testValueClassSetOf
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertTrue
+
+// ValueClassSet was created with:
+// sed -e "s/PACKAGE/androidx.collection.template/" -e "s/VALUE_CLASS/TestValueClass/g" \
+//     -e "s/vALUE_CLASS/testValueClass/g" -e "s/BACKING_PROPERTY/value.toLong()/g" \
+//     -e "s/TO_PARAM/.toULong()/g" -e "s/PRIMITIVE/Long/g" -e "s/VALUE_PKG/androidx.collection/" \
+//     collection/collection/template/ValueClassSet.kt.template \
+//     > collection/collection/src/commonTest/kotlin/androidx/collection/template/TestValueClassSet.kt
+
+@OptIn(ExperimentalUnsignedTypes::class)
+class ValueClassSetTest {
+    @Test
+    fun emptyTestValueClassSetConstructor() {
+        val set = MutableTestValueClassSet()
+        assertEquals(7, set.capacity)
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun immutableEmptyTestValueClassSet() {
+        val set: TestValueClassSet = emptyTestValueClassSet()
+        assertEquals(0, set.capacity)
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun zeroCapacityTestValueClassSet() {
+        val set = MutableTestValueClassSet(0)
+        assertEquals(0, set.capacity)
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun emptyTestValueClassSetWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val set = MutableTestValueClassSet(1800)
+        assertEquals(4095, set.capacity)
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun mutableTestValueClassSetBuilder() {
+        val empty = mutableTestValueClassSetOf()
+        assertEquals(0, empty.size)
+
+        val withElements = mutableTestValueClassSetOf(TestValueClass(1UL), TestValueClass(2UL))
+        assertEquals(2, withElements.size)
+        assertTrue(TestValueClass(1UL) in withElements)
+        assertTrue(TestValueClass(2UL) in withElements)
+    }
+
+    @Test
+    fun addToTestValueClassSet() {
+        val set = MutableTestValueClassSet()
+        set += TestValueClass(1UL)
+        assertTrue(set.add(TestValueClass(2UL)))
+
+        assertEquals(2, set.size)
+        val elements = ULongArray(2)
+        var index = 0
+        set.forEach { element ->
+            elements[index++] = element.value
+        }
+        elements.sort()
+        assertEquals(1UL, elements[0])
+        assertEquals(2UL, elements[1])
+    }
+
+    @Test
+    fun addToSizedTestValueClassSet() {
+        val set = MutableTestValueClassSet(12)
+        set += TestValueClass(1UL)
+
+        assertEquals(1, set.size)
+        assertEquals(TestValueClass(1UL), set.first())
+    }
+
+    @Test
+    fun addExistingElement() {
+        val set = MutableTestValueClassSet(12)
+        set += TestValueClass(1UL)
+        assertFalse(set.add(TestValueClass(1UL)))
+        set += TestValueClass(1UL)
+
+        assertEquals(1, set.size)
+        assertEquals(TestValueClass(1UL), set.first())
+    }
+
+    @Test
+    fun addAllTestValueClassSet() {
+        val set = mutableTestValueClassSetOf(TestValueClass(1UL))
+        assertFalse(set.addAll(mutableTestValueClassSetOf(TestValueClass(1UL))))
+        assertEquals(1, set.size)
+        assertTrue(set.addAll(mutableTestValueClassSetOf(TestValueClass(1UL), TestValueClass(2UL))))
+        assertEquals(2, set.size)
+        assertTrue(TestValueClass(2UL) in set)
+    }
+
+    @Test
+    fun plusAssignTestValueClassSet() {
+        val set = mutableTestValueClassSetOf(TestValueClass(1UL))
+        set += mutableTestValueClassSetOf(TestValueClass(1UL))
+        assertEquals(1, set.size)
+        set += mutableTestValueClassSetOf(TestValueClass(1UL), TestValueClass(2UL))
+        assertEquals(2, set.size)
+        assertTrue(TestValueClass(2UL) in set)
+    }
+
+    @Test
+    fun firstWithValue() {
+        val set = MutableTestValueClassSet()
+        set += TestValueClass(1UL)
+        set += TestValueClass(2UL)
+        var element = TestValueClass(ULong.MAX_VALUE)
+        var otherElement = TestValueClass(ULong.MAX_VALUE)
+        set.forEach { if (element.value == ULong.MAX_VALUE) element = it else otherElement = it }
+        assertEquals(element, set.first())
+        set -= element
+        assertEquals(otherElement, set.first())
+    }
+
+    @Test
+    fun firstEmpty() {
+        assertFailsWith(NoSuchElementException::class) {
+            val set = MutableTestValueClassSet()
+            set.first()
+        }
+    }
+
+    @Test
+    fun firstMatching() {
+        val set = MutableTestValueClassSet()
+        set += TestValueClass(1UL)
+        set += TestValueClass(2UL)
+        assertEquals(TestValueClass(1UL), set.first { it.value < 2UL })
+        assertEquals(TestValueClass(2UL), set.first { it.value > 1UL })
+    }
+
+    @Test
+    fun firstMatchingEmpty() {
+        assertFailsWith(NoSuchElementException::class) {
+            val set = MutableTestValueClassSet()
+            set.first { it.value > 0UL }
+        }
+    }
+
+    @Test
+    fun firstMatchingNoMatch() {
+        assertFailsWith(NoSuchElementException::class) {
+            val set = MutableTestValueClassSet()
+            set += TestValueClass(1UL)
+            set += TestValueClass(2UL)
+            set.first { it.value < 0UL }
+        }
+    }
+
+    @Test
+    fun remove() {
+        val set = MutableTestValueClassSet()
+        assertFalse(set.remove(TestValueClass(1UL)))
+
+        set += TestValueClass(1UL)
+        assertTrue(set.remove(TestValueClass(1UL)))
+        assertEquals(0, set.size)
+
+        set += TestValueClass(1UL)
+        set -= TestValueClass(1UL)
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val set = MutableTestValueClassSet(6)
+        set += TestValueClass(1UL)
+        set += TestValueClass(5UL)
+        set += TestValueClass(6UL)
+        set += TestValueClass(9UL)
+        set += TestValueClass(11UL)
+        set += TestValueClass(13UL)
+
+        // Removing all the entries will mark the medata as deleted
+        set.remove(TestValueClass(1UL))
+        set.remove(TestValueClass(5UL))
+        set.remove(TestValueClass(6UL))
+        set.remove(TestValueClass(9UL))
+        set.remove(TestValueClass(11UL))
+        set.remove(TestValueClass(13UL))
+
+        assertEquals(0, set.size)
+
+        val capacity = set.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        set += TestValueClass(3UL)
+
+        assertEquals(1, set.size)
+        assertEquals(capacity, set.capacity)
+    }
+
+    @Test
+    fun removeAllTestValueClassSet() {
+        val set = mutableTestValueClassSetOf(TestValueClass(1UL), TestValueClass(2UL))
+        assertFalse(
+            set.removeAll(
+                mutableTestValueClassSetOf(
+                    TestValueClass(3UL),
+                    TestValueClass(5UL)
+                )
+            )
+        )
+        assertEquals(2, set.size)
+        assertTrue(
+            set.removeAll(
+                mutableTestValueClassSetOf(
+                    TestValueClass(3UL),
+                    TestValueClass(1UL),
+                    TestValueClass(5UL)
+                )
+            )
+        )
+        assertEquals(1, set.size)
+        assertFalse(TestValueClass(1UL) in set)
+    }
+
+    @Test
+    fun minusAssignTestValueClassSet() {
+        val set = mutableTestValueClassSetOf(TestValueClass(1UL), TestValueClass(2UL))
+        set -= mutableTestValueClassSetOf(TestValueClass(3UL), TestValueClass(5UL))
+        assertEquals(2, set.size)
+        set -= mutableTestValueClassSetOf(
+            TestValueClass(3UL),
+            TestValueClass(1UL),
+            TestValueClass(5UL)
+        )
+        assertEquals(1, set.size)
+        assertFalse(TestValueClass(1UL) in set)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val set = MutableTestValueClassSet()
+
+        for (i in 0 until 1700) {
+            set += TestValueClass(i.toULong())
+        }
+
+        assertEquals(1700, set.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val set = MutableTestValueClassSet()
+
+            for (j in 0 until i) {
+                set += TestValueClass(j.toULong())
+            }
+
+            val elements = ULongArray(i)
+            var index = 0
+            set.forEach { element ->
+                elements[index++] = element.value
+            }
+            elements.sort()
+
+            index = 0
+            elements.forEach { element ->
+                assertEquals(element, index.toULong())
+                index++
+            }
+        }
+    }
+
+    @Test
+    fun clear() {
+        val set = MutableTestValueClassSet()
+
+        for (i in 0 until 32) {
+            set += TestValueClass(i.toULong())
+        }
+
+        val capacity = set.capacity
+        set.clear()
+
+        assertEquals(0, set.size)
+        assertEquals(capacity, set.capacity)
+    }
+
+    @Test
+    fun string() {
+        val set = MutableTestValueClassSet()
+        assertEquals("[]", set.toString())
+
+        set += TestValueClass(1UL)
+        set += TestValueClass(5UL)
+        assertTrue(
+            "[>1<, >5<]" == set.toString() ||
+                "[>5<, >1<]" == set.toString()
+        )
+    }
+
+    @Test
+    fun equals() {
+        val set = MutableTestValueClassSet()
+        set += TestValueClass(1UL)
+        set += TestValueClass(5UL)
+
+        assertFalse(set.equals(null))
+        assertEquals(set, set)
+
+        val set2 = MutableTestValueClassSet()
+        set2 += TestValueClass(5UL)
+
+        assertNotEquals(set, set2)
+
+        set2 += TestValueClass(1UL)
+        assertEquals(set, set2)
+    }
+
+    @Test
+    fun contains() {
+        val set = MutableTestValueClassSet()
+        set += TestValueClass(1UL)
+        set += TestValueClass(5UL)
+
+        assertTrue(set.contains(TestValueClass(1UL)))
+        assertTrue(set.contains(TestValueClass(5UL)))
+        assertFalse(set.contains(TestValueClass(2UL)))
+    }
+
+    @Test
+    fun empty() {
+        val set = MutableTestValueClassSet()
+        assertTrue(set.isEmpty())
+        assertFalse(set.isNotEmpty())
+        assertTrue(set.none())
+        assertFalse(set.any())
+
+        set += TestValueClass(1UL)
+
+        assertFalse(set.isEmpty())
+        assertTrue(set.isNotEmpty())
+        assertTrue(set.any())
+        assertFalse(set.none())
+    }
+
+    @Test
+    fun count() {
+        val set = MutableTestValueClassSet()
+        assertEquals(0, set.count())
+
+        set += TestValueClass(1UL)
+        assertEquals(1, set.count())
+
+        set += TestValueClass(5UL)
+        set += TestValueClass(6UL)
+        set += TestValueClass(9UL)
+        set += TestValueClass(11UL)
+        set += TestValueClass(13UL)
+
+        assertEquals(2, set.count { it.value < 6UL })
+        assertEquals(0, set.count { it.value < 0UL })
+    }
+
+    @Test
+    fun any() {
+        val set = MutableTestValueClassSet()
+        set += TestValueClass(1UL)
+        set += TestValueClass(5UL)
+        set += TestValueClass(6UL)
+        set += TestValueClass(9UL)
+        set += TestValueClass(11UL)
+        set += TestValueClass(13UL)
+
+        assertTrue(set.any { it.value >= 11UL })
+        assertFalse(set.any { it.value < 0UL })
+    }
+
+    @Test
+    fun all() {
+        val set = MutableTestValueClassSet()
+        set += TestValueClass(1UL)
+        set += TestValueClass(5UL)
+        set += TestValueClass(6UL)
+        set += TestValueClass(9UL)
+        set += TestValueClass(11UL)
+        set += TestValueClass(13UL)
+
+        assertTrue(set.all { it.value > 0UL })
+        assertFalse(set.all { it.value < 0UL })
+    }
+
+    @Test
+    fun trim() {
+        val set = mutableTestValueClassSetOf().also {
+            it += TestValueClass(1UL)
+            it += TestValueClass(2UL)
+            it += TestValueClass(3UL)
+            it += TestValueClass(4UL)
+            it += TestValueClass(5UL)
+            it += TestValueClass(7UL)
+        }
+        val capacity = set.capacity
+        assertEquals(0, set.trim())
+        set.clear()
+        assertEquals(capacity, set.trim())
+        assertEquals(0, set.capacity)
+
+        set += TestValueClass(1UL)
+        set += TestValueClass(2UL)
+        set += TestValueClass(3UL)
+        set += TestValueClass(4UL)
+        set += TestValueClass(5UL)
+        set += TestValueClass(7UL)
+        set += TestValueClass(6UL)
+        set += TestValueClass(8UL)
+        set += TestValueClass(9UL)
+        set += TestValueClass(10UL)
+        set += TestValueClass(11UL)
+        set += TestValueClass(12UL)
+        set += TestValueClass(13UL)
+        set += TestValueClass(14UL)
+
+        set -= TestValueClass(6UL)
+        set -= TestValueClass(8UL)
+        set -= TestValueClass(9UL)
+        set -= TestValueClass(10UL)
+        set -= TestValueClass(11UL)
+        set -= TestValueClass(12UL)
+        set -= TestValueClass(13UL)
+        set -= TestValueClass(14UL)
+        assertTrue(set.trim() > 0)
+        assertEquals(capacity, set.capacity)
+    }
+
+    @Test
+    fun testValueClassSetOfEmpty() {
+        assertEquals(emptyTestValueClassSet(), testValueClassSetOf())
+        assertEquals(0, testValueClassSetOf().size)
+    }
+
+    @Test
+    fun testValueClassSetOfOne() {
+        val set = testValueClassSetOf(TestValueClass(1UL))
+        assertEquals(1, set.size)
+        assertEquals(TestValueClass(1UL), set.first())
+    }
+
+    @Test
+    fun testValueClassSetOfTwo() {
+        val set = testValueClassSetOf(TestValueClass(1UL), TestValueClass(2UL))
+        assertEquals(2, set.size)
+        assertTrue(TestValueClass(1UL) in set)
+        assertTrue(TestValueClass(2UL) in set)
+        assertFalse(TestValueClass(5UL) in set)
+    }
+
+    @Test
+    fun testValueClassSetOfThree() {
+        val set = testValueClassSetOf(TestValueClass(1UL), TestValueClass(2UL), TestValueClass(3UL))
+        assertEquals(3, set.size)
+        assertTrue(TestValueClass(1UL) in set)
+        assertTrue(TestValueClass(2UL) in set)
+        assertTrue(TestValueClass(3UL) in set)
+        assertFalse(TestValueClass(5UL) in set)
+    }
+
+    @Test
+    fun mutableTestValueClassSetOfOne() {
+        val set = mutableTestValueClassSetOf(TestValueClass(1UL))
+        assertEquals(1, set.size)
+        assertEquals(TestValueClass(1UL), set.first())
+    }
+
+    @Test
+    fun mutableTestValueClassSetOfTwo() {
+        val set = mutableTestValueClassSetOf(TestValueClass(1UL), TestValueClass(2UL))
+        assertEquals(2, set.size)
+        assertTrue(TestValueClass(1UL) in set)
+        assertTrue(TestValueClass(2UL) in set)
+        assertFalse(TestValueClass(5UL) in set)
+    }
+
+    @Test
+    fun mutableTestValueClassSetOfThree() {
+        val set = mutableTestValueClassSetOf(
+            TestValueClass(1UL),
+            TestValueClass(2UL),
+            TestValueClass(3UL)
+        )
+        assertEquals(3, set.size)
+        assertTrue(TestValueClass(1UL) in set)
+        assertTrue(TestValueClass(2UL) in set)
+        assertTrue(TestValueClass(3UL) in set)
+        assertFalse(TestValueClass(5UL) in set)
+    }
+
+    @Test
+    fun asTestValueClassSet() {
+        assertEquals(
+            testValueClassSetOf(TestValueClass(1UL)),
+            mutableTestValueClassSetOf(TestValueClass(1UL)).asTestValueClassSet()
+        )
+    }
+}
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/template/TestValueClassList.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/template/TestValueClassList.kt
new file mode 100644
index 0000000..10c14dd
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/template/TestValueClassList.kt
@@ -0,0 +1,935 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:Suppress("NOTHING_TO_INLINE", "RedundantVisibilityModifier", "UnusedImport")
+/* ktlint-disable max-line-length */
+/* ktlint-disable import-ordering */
+
+package androidx.collection.template
+
+import androidx.collection.LongList
+import androidx.collection.MutableLongList
+import androidx.collection.emptyLongList
+import androidx.collection.mutableLongListOf
+import androidx.collection.TestValueClass
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+import kotlin.jvm.JvmInline
+
+// To use this template, you must substitute several strings. You can copy this and search/replace
+// or use a sed command. These properties must be changed:
+// * androidx.collection.template - target package (e.g. androidx.compose.ui.ui.collection)
+// * androidx.collection - package in which the value class resides
+// * TestValueClass - the value class contained in the list (e.g. Color or Offset)
+// * testValueClass - the value class, with the first letter lower case (e.g. color or offset)
+// * value.toLong() - the field in TestValueClass containing the backing primitive (e.g. packedValue)
+// * Long - the primitive type of the backing list (e.g. Long or Float)
+// * .toULong() - an operation done on the primitive to convert to the value class parameter
+//
+// For example, to create a ColorList:
+// sed -e "s/androidx.collection.template/androidx.compose.ui.graphics/" -e "s/TestValueClass/Color/g" \
+//     -e "s/testValueClass/color/g" -e "s/value.toLong()/value.toLong()/g" -e "s/Long/Long/g" \
+//     -e "s/.toULong()/.toULong()/g" -e "s/androidx.collection/androidx.compose.ui.graphics/g" \
+//     collection/collection/template/ValueClassList.kt.template \
+//     > compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/ColorList.kt
+
+/**
+ * [TestValueClassList] is a [List]-like collection for [TestValueClass] values. It allows retrieving
+ * the elements without boxing. [TestValueClassList] is always backed by a [MutableTestValueClassList],
+ * its [MutableList]-like subclass.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the list (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. It is also not safe to mutate during reentrancy --
+ * in the middle of a [forEach], for example. However, concurrent reads are safe.
+ */
+@OptIn(ExperimentalContracts::class)
+@JvmInline
+internal value class TestValueClassList(val list: LongList) {
+    /**
+     * The number of elements in the [TestValueClassList].
+     */
+    @get:androidx.annotation.IntRange(from = 0)
+    public inline val size: Int get() = list.size
+
+    /**
+     * Returns the last valid index in the [TestValueClassList]. This can be `-1` when the list is empty.
+     */
+    @get:androidx.annotation.IntRange(from = -1)
+    public inline val lastIndex: Int get() = list.lastIndex
+
+    /**
+     * Returns an [IntRange] of the valid indices for this [TestValueClassList].
+     */
+    public inline val indices: IntRange get() = list.indices
+
+    /**
+     * Returns `true` if the collection has no elements in it.
+     */
+    public inline fun none(): Boolean = list.none()
+
+    /**
+     * Returns `true` if there's at least one element in the collection.
+     */
+    public inline fun any(): Boolean = list.any()
+
+    /**
+     * Returns `true` if any of the elements give a `true` return value for [predicate].
+     */
+    public inline fun any(predicate: (element: TestValueClass) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        return list.any { predicate(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns `true` if any of the elements give a `true` return value for [predicate] while
+     * iterating in the reverse order.
+     */
+    public inline fun reversedAny(predicate: (element: TestValueClass) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        return list.reversedAny { predicate(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns `true` if the [TestValueClassList] contains [element] or `false` otherwise.
+     */
+    public inline operator fun contains(element: TestValueClass): Boolean =
+        list.contains(element.value.toLong())
+
+    /**
+     * Returns `true` if the [TestValueClassList] contains all elements in [elements] or `false` if
+     * one or more are missing.
+     */
+    public inline fun containsAll(elements: TestValueClassList): Boolean =
+        list.containsAll(elements.list)
+
+    /**
+     * Returns `true` if the [TestValueClassList] contains all elements in [elements] or `false` if
+     * one or more are missing.
+     */
+    public inline fun containsAll(elements: MutableTestValueClassList): Boolean =
+        list.containsAll(elements.list)
+
+    /**
+     * Returns the number of elements in this list.
+     */
+    public inline fun count(): Int = list.count()
+
+    /**
+     * Counts the number of elements matching [predicate].
+     * @return The number of elements in this list for which [predicate] returns true.
+     */
+    public inline fun count(predicate: (element: TestValueClass) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        return list.count { predicate(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns the first element in the [TestValueClassList] or throws a [NoSuchElementException] if
+     * it [isEmpty].
+     */
+    public inline fun first(): TestValueClass = TestValueClass(list.first().toULong())
+
+    /**
+     * Returns the first element in the [TestValueClassList] for which [predicate] returns `true` or
+     * throws [NoSuchElementException] if nothing matches.
+     * @see indexOfFirst
+     */
+    public inline fun first(predicate: (element: TestValueClass) -> Boolean): TestValueClass {
+        contract { callsInPlace(predicate) }
+        return TestValueClass(list.first { predicate(TestValueClass(it.toULong())) }.toULong())
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [TestValueClassList] in order.
+     * @param initial The value of `acc` for the first call to [operation] or return value if
+     * there are no elements in this list.
+     * @param operation function that takes current accumulator value and an element, and
+     * calculates the next accumulator value.
+     */
+    public inline fun <R> fold(initial: R, operation: (acc: R, element: TestValueClass) -> R): R {
+        contract { callsInPlace(operation) }
+        return list.fold(initial) { acc, element ->
+            operation(acc, TestValueClass(element.toULong()))
+        }
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [TestValueClassList] in order.
+     */
+    public inline fun <R> foldIndexed(
+        initial: R,
+        operation: (index: Int, acc: R, element: TestValueClass) -> R
+    ): R {
+        contract { callsInPlace(operation) }
+        return list.foldIndexed(initial) { index, acc, element ->
+            operation(index, acc, TestValueClass(element.toULong()))
+        }
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [TestValueClassList] in reverse order.
+     * @param initial The value of `acc` for the first call to [operation] or return value if
+     * there are no elements in this list.
+     * @param operation function that takes an element and the current accumulator value, and
+     * calculates the next accumulator value.
+     */
+    public inline fun <R> foldRight(initial: R, operation: (element: TestValueClass, acc: R) -> R): R {
+        contract { callsInPlace(operation) }
+        return list.foldRight(initial) { element, acc ->
+            operation(TestValueClass(element.toULong()), acc)
+        }
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [TestValueClassList] in reverse order.
+     */
+    public inline fun <R> foldRightIndexed(
+        initial: R,
+        operation: (index: Int, element: TestValueClass, acc: R) -> R
+    ): R {
+        contract { callsInPlace(operation) }
+        return list.foldRightIndexed(initial) { index, element, acc ->
+            operation(index, TestValueClass(element.toULong()), acc)
+        }
+    }
+
+    /**
+     * Calls [block] for each element in the [TestValueClassList], in order.
+     * @param block will be executed for every element in the list, accepting an element from
+     * the list
+     */
+    public inline fun forEach(block: (element: TestValueClass) -> Unit) {
+        contract { callsInPlace(block) }
+        list.forEach { block(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Calls [block] for each element in the [TestValueClassList] along with its index, in order.
+     * @param block will be executed for every element in the list, accepting the index and
+     * the element at that index.
+     */
+    public inline fun forEachIndexed(block: (index: Int, element: TestValueClass) -> Unit) {
+        contract { callsInPlace(block) }
+        list.forEachIndexed { index, element ->
+            block(index, TestValueClass(element.toULong()))
+        }
+    }
+
+    /**
+     * Calls [block] for each element in the [TestValueClassList] in reverse order.
+     * @param block will be executed for every element in the list, accepting an element from
+     * the list
+     */
+    public inline fun forEachReversed(block: (element: TestValueClass) -> Unit) {
+        contract { callsInPlace(block) }
+        list.forEachReversed { block(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Calls [block] for each element in the [TestValueClassList] along with its index, in reverse
+     * order.
+     * @param block will be executed for every element in the list, accepting the index and
+     * the element at that index.
+     */
+    public inline fun forEachReversedIndexed(block: (index: Int, element: TestValueClass) -> Unit) {
+        contract { callsInPlace(block) }
+        list.forEachReversedIndexed { index, element ->
+            block(index, TestValueClass(element.toULong()))
+        }
+    }
+
+    /**
+     * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if
+     * the [index] is out of bounds of this collection.
+     */
+    public inline operator fun get(
+        @androidx.annotation.IntRange(from = 0) index: Int
+    ): TestValueClass = TestValueClass(list[index].toULong())
+
+    /**
+     * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if
+     * the [index] is out of bounds of this collection.
+     */
+    public inline fun elementAt(@androidx.annotation.IntRange(from = 0) index: Int): TestValueClass =
+        TestValueClass(list[index].toULong())
+
+    /**
+     * Returns the element at the given [index] or [defaultValue] if [index] is out of bounds
+     * of the collection.
+     * @param index The index of the element whose value should be returned
+     * @param defaultValue A lambda to call with [index] as a parameter to return a value at
+     * an index not in the list.
+     */
+    public inline fun elementAtOrElse(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        defaultValue: (index: Int) -> TestValueClass
+    ): TestValueClass =
+        TestValueClass(list.elementAtOrElse(index) { defaultValue(it).value.toLong() }.toULong())
+
+    /**
+     * Returns the index of [element] in the [TestValueClassList] or `-1` if [element] is not there.
+     */
+    public inline fun indexOf(element: TestValueClass): Int =
+        list.indexOf(element.value.toLong())
+
+    /**
+     * Returns the index if the first element in the [TestValueClassList] for which [predicate]
+     * returns `true`.
+     */
+    public inline fun indexOfFirst(predicate: (element: TestValueClass) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        return list.indexOfFirst { predicate(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns the index if the last element in the [TestValueClassList] for which [predicate]
+     * returns `true`.
+     */
+    public inline fun indexOfLast(predicate: (element: TestValueClass) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        return list.indexOfLast { predicate(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns `true` if the [TestValueClassList] has no elements in it or `false` otherwise.
+     */
+    public inline fun isEmpty(): Boolean = list.isEmpty()
+
+    /**
+     * Returns `true` if there are elements in the [TestValueClassList] or `false` if it is empty.
+     */
+    public inline fun isNotEmpty(): Boolean = list.isNotEmpty()
+
+    /**
+     * Returns the last element in the [TestValueClassList] or throws a [NoSuchElementException] if
+     * it [isEmpty].
+     */
+    public inline fun last(): TestValueClass = TestValueClass(list.last().toULong())
+
+    /**
+     * Returns the last element in the [TestValueClassList] for which [predicate] returns `true` or
+     * throws [NoSuchElementException] if nothing matches.
+     * @see indexOfLast
+     */
+    public inline fun last(predicate: (element: TestValueClass) -> Boolean): TestValueClass {
+        contract { callsInPlace(predicate) }
+        return TestValueClass(list.last { predicate(TestValueClass(it.toULong())) }.toULong())
+    }
+
+    /**
+     * Returns the index of the last element in the [TestValueClassList] that is the same as
+     * [element] or `-1` if no elements match.
+     */
+    public inline fun lastIndexOf(element: TestValueClass): Int =
+        list.lastIndexOf(element.value.toLong())
+
+    /**
+     * Returns a String representation of the list, surrounded by "[]" and each element
+     * separated by ", ".
+     */
+    override fun toString(): String {
+        if (isEmpty()) {
+            return "[]"
+        }
+        return buildString {
+            append('[')
+            forEachIndexed { index: Int, element: TestValueClass ->
+                if (index != 0) {
+                    append(',').append(' ')
+                }
+                append(element)
+            }
+            append(']')
+        }
+    }
+}
+
+/**
+ * [MutableTestValueClassList] is a [MutableList]-like collection for [TestValueClass] values.
+ * It allows storing and retrieving the elements without boxing. Immutable
+ * access is available through its base class [TestValueClassList], which has a [List]-like
+ * interface.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the list (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. It is also not safe to mutate during reentrancy --
+ * in the middle of a [forEach], for example. However, concurrent reads are safe.
+ *
+ * @constructor Creates a [MutableTestValueClassList] with a [capacity] of `initialCapacity`.
+ */
+@OptIn(ExperimentalContracts::class)
+@JvmInline
+internal value class MutableTestValueClassList(val list: MutableLongList) {
+    public constructor(initialCapacity: Int = 16) : this(MutableLongList(initialCapacity))
+
+    /**
+     * The number of elements in the [TestValueClassList].
+     */
+    @get:androidx.annotation.IntRange(from = 0)
+    public inline val size: Int get() = list.size
+
+    /**
+     * Returns the last valid index in the [TestValueClassList]. This can be `-1` when the list is empty.
+     */
+    @get:androidx.annotation.IntRange(from = -1)
+    public inline val lastIndex: Int get() = list.lastIndex
+
+    /**
+     * Returns an [IntRange] of the valid indices for this [TestValueClassList].
+     */
+    public inline val indices: IntRange get() = list.indices
+
+    /**
+     * Returns `true` if the collection has no elements in it.
+     */
+    public inline fun none(): Boolean = list.none()
+
+    /**
+     * Returns `true` if there's at least one element in the collection.
+     */
+    public inline fun any(): Boolean = list.any()
+
+    /**
+     * Returns `true` if any of the elements give a `true` return value for [predicate].
+     */
+    public inline fun any(predicate: (element: TestValueClass) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        return list.any { predicate(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns `true` if any of the elements give a `true` return value for [predicate] while
+     * iterating in the reverse order.
+     */
+    public inline fun reversedAny(predicate: (element: TestValueClass) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        return list.reversedAny { predicate(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns `true` if the [TestValueClassList] contains [element] or `false` otherwise.
+     */
+    public inline operator fun contains(element: TestValueClass): Boolean =
+        list.contains(element.value.toLong())
+
+    /**
+     * Returns `true` if the [TestValueClassList] contains all elements in [elements] or `false` if
+     * one or more are missing.
+     */
+    public inline fun containsAll(elements: TestValueClassList): Boolean =
+        list.containsAll(elements.list)
+
+    /**
+     * Returns `true` if the [TestValueClassList] contains all elements in [elements] or `false` if
+     * one or more are missing.
+     */
+    public inline fun containsAll(elements: MutableTestValueClassList): Boolean =
+        list.containsAll(elements.list)
+
+    /**
+     * Returns the number of elements in this list.
+     */
+    public inline fun count(): Int = list.count()
+
+    /**
+     * Counts the number of elements matching [predicate].
+     * @return The number of elements in this list for which [predicate] returns true.
+     */
+    public inline fun count(predicate: (element: TestValueClass) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        return list.count { predicate(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns the first element in the [TestValueClassList] or throws a [NoSuchElementException] if
+     * it [isEmpty].
+     */
+    public inline fun first(): TestValueClass = TestValueClass(list.first().toULong())
+
+    /**
+     * Returns the first element in the [TestValueClassList] for which [predicate] returns `true` or
+     * throws [NoSuchElementException] if nothing matches.
+     * @see indexOfFirst
+     */
+    public inline fun first(predicate: (element: TestValueClass) -> Boolean): TestValueClass {
+        contract { callsInPlace(predicate) }
+        return TestValueClass(list.first { predicate(TestValueClass(it.toULong())) }.toULong())
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [TestValueClassList] in order.
+     * @param initial The value of `acc` for the first call to [operation] or return value if
+     * there are no elements in this list.
+     * @param operation function that takes current accumulator value and an element, and
+     * calculates the next accumulator value.
+     */
+    public inline fun <R> fold(initial: R, operation: (acc: R, element: TestValueClass) -> R): R {
+        contract { callsInPlace(operation) }
+        return list.fold(initial) { acc, element ->
+            operation(acc, TestValueClass(element.toULong()))
+        }
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [TestValueClassList] in order.
+     */
+    public inline fun <R> foldIndexed(
+        initial: R,
+        operation: (index: Int, acc: R, element: TestValueClass) -> R
+    ): R {
+        contract { callsInPlace(operation) }
+        return list.foldIndexed(initial) { index, acc, element ->
+            operation(index, acc, TestValueClass(element.toULong()))
+        }
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [TestValueClassList] in reverse order.
+     * @param initial The value of `acc` for the first call to [operation] or return value if
+     * there are no elements in this list.
+     * @param operation function that takes an element and the current accumulator value, and
+     * calculates the next accumulator value.
+     */
+    public inline fun <R> foldRight(initial: R, operation: (element: TestValueClass, acc: R) -> R): R {
+        contract { callsInPlace(operation) }
+        return list.foldRight(initial) { element, acc ->
+            operation(TestValueClass(element.toULong()), acc)
+        }
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [TestValueClassList] in reverse order.
+     */
+    public inline fun <R> foldRightIndexed(
+        initial: R,
+        operation: (index: Int, element: TestValueClass, acc: R) -> R
+    ): R {
+        contract { callsInPlace(operation) }
+        return list.foldRightIndexed(initial) { index, element, acc ->
+            operation(index, TestValueClass(element.toULong()), acc)
+        }
+    }
+
+    /**
+     * Calls [block] for each element in the [TestValueClassList], in order.
+     * @param block will be executed for every element in the list, accepting an element from
+     * the list
+     */
+    public inline fun forEach(block: (element: TestValueClass) -> Unit) {
+        contract { callsInPlace(block) }
+        list.forEach { block(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Calls [block] for each element in the [TestValueClassList] along with its index, in order.
+     * @param block will be executed for every element in the list, accepting the index and
+     * the element at that index.
+     */
+    public inline fun forEachIndexed(block: (index: Int, element: TestValueClass) -> Unit) {
+        contract { callsInPlace(block) }
+        list.forEachIndexed { index, element ->
+            block(index, TestValueClass(element.toULong()))
+        }
+    }
+
+    /**
+     * Calls [block] for each element in the [TestValueClassList] in reverse order.
+     * @param block will be executed for every element in the list, accepting an element from
+     * the list
+     */
+    public inline fun forEachReversed(block: (element: TestValueClass) -> Unit) {
+        contract { callsInPlace(block) }
+        list.forEachReversed { block(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Calls [block] for each element in the [TestValueClassList] along with its index, in reverse
+     * order.
+     * @param block will be executed for every element in the list, accepting the index and
+     * the element at that index.
+     */
+    public inline fun forEachReversedIndexed(block: (index: Int, element: TestValueClass) -> Unit) {
+        contract { callsInPlace(block) }
+        list.forEachReversedIndexed { index, element ->
+            block(index, TestValueClass(element.toULong()))
+        }
+    }
+
+    /**
+     * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if
+     * the [index] is out of bounds of this collection.
+     */
+    public inline operator fun get(
+        @androidx.annotation.IntRange(from = 0) index: Int
+    ): TestValueClass = TestValueClass(list[index].toULong())
+
+    /**
+     * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if
+     * the [index] is out of bounds of this collection.
+     */
+    public inline fun elementAt(@androidx.annotation.IntRange(from = 0) index: Int): TestValueClass =
+        TestValueClass(list[index].toULong())
+
+    /**
+     * Returns the element at the given [index] or [defaultValue] if [index] is out of bounds
+     * of the collection.
+     * @param index The index of the element whose value should be returned
+     * @param defaultValue A lambda to call with [index] as a parameter to return a value at
+     * an index not in the list.
+     */
+    public inline fun elementAtOrElse(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        defaultValue: (index: Int) -> TestValueClass
+    ): TestValueClass =
+        TestValueClass(list.elementAtOrElse(index) { defaultValue(it).value.toLong() }.toULong())
+
+    /**
+     * Returns the index of [element] in the [TestValueClassList] or `-1` if [element] is not there.
+     */
+    public inline fun indexOf(element: TestValueClass): Int =
+        list.indexOf(element.value.toLong())
+
+    /**
+     * Returns the index if the first element in the [TestValueClassList] for which [predicate]
+     * returns `true`.
+     */
+    public inline fun indexOfFirst(predicate: (element: TestValueClass) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        return list.indexOfFirst { predicate(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns the index if the last element in the [TestValueClassList] for which [predicate]
+     * returns `true`.
+     */
+    public inline fun indexOfLast(predicate: (element: TestValueClass) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        return list.indexOfLast { predicate(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns `true` if the [TestValueClassList] has no elements in it or `false` otherwise.
+     */
+    public inline fun isEmpty(): Boolean = list.isEmpty()
+
+    /**
+     * Returns `true` if there are elements in the [TestValueClassList] or `false` if it is empty.
+     */
+    public inline fun isNotEmpty(): Boolean = list.isNotEmpty()
+
+    /**
+     * Returns the last element in the [TestValueClassList] or throws a [NoSuchElementException] if
+     * it [isEmpty].
+     */
+    public inline fun last(): TestValueClass = TestValueClass(list.last().toULong())
+
+    /**
+     * Returns the last element in the [TestValueClassList] for which [predicate] returns `true` or
+     * throws [NoSuchElementException] if nothing matches.
+     * @see indexOfLast
+     */
+    public inline fun last(predicate: (element: TestValueClass) -> Boolean): TestValueClass {
+        contract { callsInPlace(predicate) }
+        return TestValueClass(list.last { predicate(TestValueClass(it.toULong())) }.toULong())
+    }
+
+    /**
+     * Returns the index of the last element in the [TestValueClassList] that is the same as
+     * [element] or `-1` if no elements match.
+     */
+    public inline fun lastIndexOf(element: TestValueClass): Int =
+        list.lastIndexOf(element.value.toLong())
+
+    /**
+     * Returns a String representation of the list, surrounded by "[]" and each element
+     * separated by ", ".
+     */
+    override fun toString(): String = asTestValueClassList().toString()
+
+    /**
+     * Returns a read-only interface to the list.
+     */
+    public inline fun asTestValueClassList(): TestValueClassList = TestValueClassList(list)
+
+    /**
+     * Returns the total number of elements that can be held before the [MutableTestValueClassList] must
+     * grow.
+     *
+     * @see ensureCapacity
+     */
+    public inline val capacity: Int
+        get() = list.capacity
+
+    /**
+     * Adds [element] to the [MutableTestValueClassList] and returns `true`.
+     */
+    public inline fun add(element: TestValueClass): Boolean =
+        list.add(element.value.toLong())
+
+    /**
+     * Adds [element] to the [MutableTestValueClassList] at the given [index], shifting over any
+     * elements at [index] and after, if any.
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
+     */
+    public inline fun add(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        element: TestValueClass
+    ) = list.add(index, element.value.toLong())
+
+    /**
+     * Adds all [elements] to the [MutableTestValueClassList] at the given [index], shifting over any
+     * elements at [index] and after, if any.
+     * @return `true` if the [MutableTestValueClassList] was changed or `false` if [elements] was empty
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
+     */
+    public inline fun addAll(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        elements: TestValueClassList
+    ): Boolean = list.addAll(index, elements.list)
+
+    /**
+     * Adds all [elements] to the [MutableTestValueClassList] at the given [index], shifting over any
+     * elements at [index] and after, if any.
+     * @return `true` if the [MutableTestValueClassList] was changed or `false` if [elements] was empty
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
+     */
+    public inline fun addAll(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        elements: MutableTestValueClassList
+    ): Boolean = list.addAll(index, elements.list)
+
+    /**
+     * Adds all [elements] to the end of the [MutableTestValueClassList] and returns `true` if the
+     * [MutableTestValueClassList] was changed or `false` if [elements] was empty.
+     */
+    public inline fun addAll(elements: TestValueClassList): Boolean = list.addAll(elements.list)
+
+    /**
+     * Adds all [elements] to the end of the [MutableTestValueClassList].
+     */
+    public inline operator fun plusAssign(elements: TestValueClassList) =
+        list.plusAssign(elements.list)
+
+    /**
+     * Adds all [elements] to the end of the [MutableTestValueClassList] and returns `true` if the
+     * [MutableTestValueClassList] was changed or `false` if [elements] was empty.
+     */
+    public inline fun addAll(elements: MutableTestValueClassList): Boolean = list.addAll(elements.list)
+
+    /**
+     * Adds all [elements] to the end of the [MutableTestValueClassList].
+     */
+    public inline operator fun plusAssign(elements: MutableTestValueClassList) =
+        list.plusAssign(elements.list)
+
+    /**
+     * Removes all elements in the [MutableTestValueClassList]. The storage isn't released.
+     * @see trim
+     */
+    public inline fun clear() = list.clear()
+
+    /**
+     * Reduces the internal storage. If [capacity] is greater than [minCapacity] and [size], the
+     * internal storage is reduced to the maximum of [size] and [minCapacity].
+     * @see ensureCapacity
+     */
+    public inline fun trim(minCapacity: Int = size) = list.trim(minCapacity)
+
+    /**
+     * Ensures that there is enough space to store [capacity] elements in the [MutableTestValueClassList].
+     * @see trim
+     */
+    public inline fun ensureCapacity(capacity: Int) = list.ensureCapacity(capacity)
+
+    /**
+     * [add] [element] to the [MutableTestValueClassList].
+     */
+    public inline operator fun plusAssign(element: TestValueClass) =
+        list.plusAssign(element.value.toLong())
+
+    /**
+     * [remove] [element] from the [MutableTestValueClassList]
+     */
+    public inline operator fun minusAssign(element: TestValueClass) =
+        list.minusAssign(element.value.toLong())
+
+    /**
+     * Removes [element] from the [MutableTestValueClassList]. If [element] was in the [MutableTestValueClassList]
+     * and was removed, `true` will be returned, or `false` will be returned if the element
+     * was not found.
+     */
+    public inline fun remove(element: TestValueClass): Boolean =
+        list.remove(element.value.toLong())
+
+    /**
+     * Removes all [elements] from the [MutableTestValueClassList] and returns `true` if anything was removed.
+     */
+    public inline fun removeAll(elements: TestValueClassList): Boolean =
+        list.removeAll(elements.list)
+
+    /**
+     * Removes all [elements] from the [MutableTestValueClassList].
+     */
+    public inline operator fun minusAssign(elements: TestValueClassList) =
+        list.minusAssign(elements.list)
+
+    /**
+     * Removes all [elements] from the [MutableTestValueClassList] and returns `true` if anything was removed.
+     */
+    public inline fun removeAll(elements: MutableTestValueClassList): Boolean =
+        list.removeAll(elements.list)
+
+    /**
+     * Removes all [elements] from the [MutableTestValueClassList].
+     */
+    public inline operator fun minusAssign(elements: MutableTestValueClassList) =
+        list.minusAssign(elements.list)
+
+    /**
+     * Removes the element at the given [index] and returns it.
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [lastIndex], inclusive
+     */
+    public inline fun removeAt(@androidx.annotation.IntRange(from = 0) index: Int): TestValueClass =
+        TestValueClass(list.removeAt(index).toULong())
+
+    /**
+     * Removes items from index [start] (inclusive) to [end] (exclusive).
+     * @throws IndexOutOfBoundsException if [start] or [end] isn't between 0 and [size], inclusive
+     * @throws IllegalArgumentException if [start] is greater than [end]
+     */
+    public inline fun removeRange(
+        @androidx.annotation.IntRange(from = 0) start: Int,
+        @androidx.annotation.IntRange(from = 0) end: Int
+    ) = list.removeRange(start, end)
+
+    /**
+     * Keeps only [elements] in the [MutableTestValueClassList] and removes all other values.
+     * @return `true` if the [MutableTestValueClassList] has changed.
+     */
+    public inline fun retainAll(elements: TestValueClassList): Boolean =
+        list.retainAll(elements.list)
+
+    /**
+     * Keeps only [elements] in the [MutableTestValueClassList] and removes all other values.
+     * @return `true` if the [MutableTestValueClassList] has changed.
+     */
+    public inline fun retainAll(elements: MutableTestValueClassList): Boolean =
+        list.retainAll(elements.list)
+
+    /**
+     * Sets the value at [index] to [element].
+     * @return the previous value set at [index]
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [lastIndex], inclusive
+     */
+    public inline operator fun set(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        element: TestValueClass
+    ): TestValueClass = TestValueClass(list.set(index, element.value.toLong()).toULong())
+}
+
+/**
+ * @return a read-only [TestValueClassList] with nothing in it.
+ */
+internal inline fun emptyTestValueClassList(): TestValueClassList = TestValueClassList(emptyLongList())
+
+/**
+ * @return a read-only [TestValueClassList] with nothing in it.
+ */
+internal inline fun testValueClassListOf(): TestValueClassList = TestValueClassList(emptyLongList())
+
+/**
+ * @return a new read-only [TestValueClassList] with [element1] as the only item in the list.
+ */
+internal inline fun testValueClassListOf(element1: TestValueClass): TestValueClassList =
+    TestValueClassList(mutableLongListOf(element1.value.toLong()))
+
+/**
+ * @return a new read-only [TestValueClassList] with 2 elements, [element1] and [element2], in order.
+ */
+internal inline fun testValueClassListOf(element1: TestValueClass, element2: TestValueClass): TestValueClassList =
+    TestValueClassList(
+        mutableLongListOf(
+            element1.value.toLong(),
+            element2.value.toLong()
+        )
+    )
+
+/**
+ * @return a new read-only [TestValueClassList] with 3 elements, [element1], [element2], and [element3],
+ * in order.
+ */
+internal inline fun testValueClassListOf(
+        element1: TestValueClass,
+        element2: TestValueClass,
+        element3: TestValueClass
+): TestValueClassList = TestValueClassList(
+    mutableLongListOf(
+        element1.value.toLong(),
+        element2.value.toLong(),
+        element3.value.toLong()
+    )
+)
+
+/**
+ * @return a new empty [MutableTestValueClassList] with the default capacity.
+ */
+internal inline fun mutableTestValueClassListOf(): MutableTestValueClassList =
+    MutableTestValueClassList(MutableLongList())
+
+/**
+ * @return a new [MutableTestValueClassList] with [element1] as the only item in the list.
+ */
+internal inline fun mutableTestValueClassListOf(element1: TestValueClass): MutableTestValueClassList =
+    MutableTestValueClassList(mutableLongListOf(element1.value.toLong()))
+
+/**
+ * @return a new [MutableTestValueClassList] with 2 elements, [element1] and [element2], in order.
+ */
+internal inline fun mutableTestValueClassListOf(
+        element1: TestValueClass,
+        element2: TestValueClass
+    ): MutableTestValueClassList = MutableTestValueClassList(
+        mutableLongListOf(
+            element1.value.toLong(),
+            element2.value.toLong()
+        )
+    )
+
+/**
+ * @return a new [MutableTestValueClassList] with 3 elements, [element1], [element2], and [element3],
+ * in order.
+ */
+internal inline fun mutableTestValueClassListOf(
+        element1: TestValueClass,
+        element2: TestValueClass,
+        element3: TestValueClass
+): MutableTestValueClassList = MutableTestValueClassList(
+    mutableLongListOf(
+        element1.value.toLong(),
+        element2.value.toLong(),
+        element3.value.toLong()
+    )
+)
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/template/TestValueClassSet.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/template/TestValueClassSet.kt
new file mode 100644
index 0000000..b01d2b5
--- /dev/null
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/template/TestValueClassSet.kt
@@ -0,0 +1,554 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "KotlinRedundantDiagnosticSuppress",
+    "KotlinConstantConditions",
+    "PropertyName",
+    "ConstPropertyName",
+    "PrivatePropertyName",
+    "NOTHING_TO_INLINE",
+    "UnusedImport"
+)
+
+package androidx.collection.template
+
+/* ktlint-disable max-line-length */
+/* ktlint-disable import-ordering */
+
+import androidx.collection.LongSet
+import androidx.collection.MutableLongSet
+import androidx.collection.emptyLongSet
+import androidx.collection.mutableLongSetOf
+import androidx.collection.TestValueClass
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+import kotlin.jvm.JvmInline
+
+/* ktlint-disable max-line-length */
+// To use this template, you must substitute several strings. You can copy this and search/replace
+// or use a sed command. These properties must be changed:
+// * androidx.collection.template - target package (e.g. androidx.compose.ui.ui.collection)
+// * androidx.collection - package in which the value class resides
+// * TestValueClass - the value class contained in the set (e.g. Color or Offset)
+// * testValueClass - the value class, with the first letter lower case (e.g. color or offset)
+// * value.toLong() - the field in TestValueClass containing the backing primitive (e.g. packedValue)
+// * Long - the primitive type of the backing set (e.g. Long or Float)
+// * .toULong() - an operation done on the primitive to convert to the value class parameter
+//
+// For example, to create a ColorSet:
+// sed -e "s/androidx.collection.template/androidx.compose.ui.graphics/" -e "s/TestValueClass/Color/g" \
+//     -e "s/testValueClass/color/g" -e "s/value.toLong()/value.toLong()/g" -e "s/Long/Long/g" \
+//     -e "s/.toULong()/.toULong()/g" -e "s/androidx.collection/androidx.collection/g" \
+//     collection/collection/template/ValueClassSet.kt.template \
+//     > compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/ColorSet.kt
+
+/**
+ * Returns an empty, read-only [TestValueClassSet].
+ */
+internal inline fun emptyTestValueClassSet(): TestValueClassSet = TestValueClassSet(emptyLongSet())
+
+/**
+ * Returns an empty, read-only [TestValueClassSet].
+ */
+internal inline fun testValueClassSetOf(): TestValueClassSet = TestValueClassSet(emptyLongSet())
+
+/**
+ * Returns a new read-only [TestValueClassSet] with only [element1] in it.
+ */
+internal inline fun testValueClassSetOf(element1: TestValueClass): TestValueClassSet =
+    TestValueClassSet(mutableLongSetOf(element1.value.toLong()))
+
+/**
+ * Returns a new read-only [TestValueClassSet] with only [element1] and [element2] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+internal fun testValueClassSetOf(
+    element1: TestValueClass,
+    element2: TestValueClass
+): TestValueClassSet =
+    TestValueClassSet(
+        mutableLongSetOf(
+            element1.value.toLong(),
+            element2.value.toLong(),
+        )
+    )
+
+/**
+ * Returns a new read-only [TestValueClassSet] with only [element1], [element2], and [element3] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+internal fun testValueClassSetOf(
+    element1: TestValueClass,
+    element2: TestValueClass,
+    element3: TestValueClass
+): TestValueClassSet = TestValueClassSet(
+    mutableLongSetOf(
+        element1.value.toLong(),
+        element2.value.toLong(),
+        element3.value.toLong(),
+    )
+)
+
+/**
+ * Returns a new [MutableTestValueClassSet].
+ */
+internal fun mutableTestValueClassSetOf(): MutableTestValueClassSet = MutableTestValueClassSet(
+    MutableLongSet()
+)
+
+/**
+ * Returns a new [MutableTestValueClassSet] with only [element1] in it.
+ */
+internal fun mutableTestValueClassSetOf(element1: TestValueClass): MutableTestValueClassSet =
+    MutableTestValueClassSet(mutableLongSetOf(element1.value.toLong()))
+
+/**
+ * Returns a new [MutableTestValueClassSet] with only [element1] and [element2] in it.
+ */
+internal fun mutableTestValueClassSetOf(
+    element1: TestValueClass,
+    element2: TestValueClass
+): MutableTestValueClassSet =
+    MutableTestValueClassSet(
+        mutableLongSetOf(
+            element1.value.toLong(),
+            element2.value.toLong(),
+        )
+    )
+
+/**
+ * Returns a new [MutableTestValueClassSet] with only [element1], [element2], and [element3] in it.
+ */
+internal fun mutableTestValueClassSetOf(
+    element1: TestValueClass,
+    element2: TestValueClass,
+    element3: TestValueClass
+): MutableTestValueClassSet =
+    MutableTestValueClassSet(
+        mutableLongSetOf(
+            element1.value.toLong(),
+            element2.value.toLong(),
+            element3.value.toLong(),
+        )
+    )
+
+/**
+ * [TestValueClassSet] is a container with a [Set]-like interface designed to avoid
+ * allocations, including boxing.
+ *
+ * This implementation makes no guarantee as to the order of the elements,
+ * nor does it make guarantees that the order remains constant over time.
+ *
+ * Though [TestValueClassSet] offers a read-only interface, it is always backed
+ * by a [MutableTestValueClassSet]. Read operations alone are thread-safe. However,
+ * any mutations done through the backing [MutableTestValueClassSet] while reading
+ * on another thread are not safe and the developer must protect the set
+ * from such changes during read operations.
+ *
+ * @see [MutableTestValueClassSet]
+ */
+@OptIn(ExperimentalContracts::class)
+@JvmInline
+internal value class TestValueClassSet(val set: LongSet) {
+    /**
+     * Returns the number of elements that can be stored in this set
+     * without requiring internal storage reallocation.
+     */
+    @get:androidx.annotation.IntRange(from = 0)
+    public inline val capacity: Int
+        get() = set.capacity
+
+    /**
+     * Returns the number of elements in this set.
+     */
+    @get:androidx.annotation.IntRange(from = 0)
+    public inline val size: Int
+        get() = set.size
+
+    /**
+     * Returns `true` if this set has at least one element.
+     */
+    public inline fun any(): Boolean = set.any()
+
+    /**
+     * Returns `true` if this set has no elements.
+     */
+    public inline fun none(): Boolean = set.none()
+
+    /**
+     * Indicates whether this set is empty.
+     */
+    public inline fun isEmpty(): Boolean = set.isEmpty()
+
+    /**
+     * Returns `true` if this set is not empty.
+     */
+    public inline fun isNotEmpty(): Boolean = set.isNotEmpty()
+
+    /**
+     * Returns the first element in the collection.
+     * @throws NoSuchElementException if the collection is empty
+     */
+    public inline fun first(): TestValueClass = TestValueClass(set.first().toULong())
+
+    /**
+     * Returns the first element in the collection for which [predicate] returns `true`.
+     *
+     * **Note** There is no mechanism for both determining if there is an element that matches
+     * [predicate] _and_ returning it if it exists. Developers should use [forEach] to achieve
+     * this behavior.
+     *
+     * @param predicate Called on elements of the set, returning `true` for an element that matches
+     * or `false` if it doesn't
+     * @return An element in the set for which [predicate] returns `true`.
+     * @throws NoSuchElementException if [predicate] returns `false` for all elements or the
+     * collection is empty.
+     */
+    public inline fun first(predicate: (element: TestValueClass) -> Boolean): TestValueClass =
+        TestValueClass(set.first { predicate(TestValueClass(it.toULong())) }.toULong())
+
+    /**
+     * Iterates over every element stored in this set by invoking
+     * the specified [block] lambda.
+     * @param block called with each element in the set
+     */
+    public inline fun forEach(block: (element: TestValueClass) -> Unit) {
+        contract { callsInPlace(block) }
+        set.forEach { block(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns true if all elements match the given [predicate].
+     * @param predicate called for elements in the set to determine if it returns return `true` for
+     * all elements.
+     */
+    public inline fun all(predicate: (element: TestValueClass) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        return set.all { predicate(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns true if at least one element matches the given [predicate].
+     * @param predicate called for elements in the set to determine if it returns `true` for any
+     * elements.
+     */
+    public inline fun any(predicate: (element: TestValueClass) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        return set.any { predicate(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns the number of elements in this set.
+     */
+    @androidx.annotation.IntRange(from = 0)
+    public inline fun count(): Int = set.count()
+
+    /**
+     * Returns the number of elements matching the given [predicate].
+     * @param predicate Called for all elements in the set to count the number for which it returns
+     * `true`.
+     */
+    @androidx.annotation.IntRange(from = 0)
+    public inline fun count(predicate: (element: TestValueClass) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        return set.count { predicate(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns `true` if the specified [element] is present in this set, `false`
+     * otherwise.
+     * @param element The element to look for in this set
+     */
+    public inline operator fun contains(element: TestValueClass): Boolean =
+        set.contains(element.value.toLong())
+
+    /**
+     * Returns a string representation of this set. The set is denoted in the
+     * string by the `{}`. Each element is separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "[]"
+        }
+
+        val s = StringBuilder().append('[')
+        var index = 0
+        forEach { element ->
+            if (index++ != 0) {
+                s.append(',').append(' ')
+            }
+            s.append(element)
+        }
+        return s.append(']').toString()
+    }
+}
+
+/**
+ * [MutableTestValueClassSet] is a container with a [MutableSet]-like interface based on a flat
+ * hash table implementation. The underlying implementation is designed to avoid
+ * all allocations on insertion, removal, retrieval, and iteration. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added elements to the set.
+ *
+ * This implementation makes no guarantee as to the order of the elements stored,
+ * nor does it make guarantees that the order remains constant over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the set (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Concurrent reads are however safe.
+ */
+@OptIn(ExperimentalContracts::class)
+@JvmInline
+internal value class MutableTestValueClassSet(val set: MutableLongSet) {
+    /**
+     * Returns the number of elements that can be stored in this set
+     * without requiring internal storage reallocation.
+     */
+    @get:androidx.annotation.IntRange(from = 0)
+    public inline val capacity: Int
+        get() = set.capacity
+
+    /**
+     * Returns the number of elements in this set.
+     */
+    @get:androidx.annotation.IntRange(from = 0)
+    public inline val size: Int
+        get() = set.size
+
+    /**
+     * Returns `true` if this set has at least one element.
+     */
+    public inline fun any(): Boolean = set.any()
+
+    /**
+     * Returns `true` if this set has no elements.
+     */
+    public inline fun none(): Boolean = set.none()
+
+    /**
+     * Indicates whether this set is empty.
+     */
+    public inline fun isEmpty(): Boolean = set.isEmpty()
+
+    /**
+     * Returns `true` if this set is not empty.
+     */
+    public inline fun isNotEmpty(): Boolean = set.isNotEmpty()
+
+    /**
+     * Returns the first element in the collection.
+     * @throws NoSuchElementException if the collection is empty
+     */
+    public inline fun first(): TestValueClass = TestValueClass(set.first().toULong())
+
+    /**
+     * Returns the first element in the collection for which [predicate] returns `true`.
+     *
+     * **Note** There is no mechanism for both determining if there is an element that matches
+     * [predicate] _and_ returning it if it exists. Developers should use [forEach] to achieve
+     * this behavior.
+     *
+     * @param predicate Called on elements of the set, returning `true` for an element that matches
+     * or `false` if it doesn't
+     * @return An element in the set for which [predicate] returns `true`.
+     * @throws NoSuchElementException if [predicate] returns `false` for all elements or the
+     * collection is empty.
+     */
+    public inline fun first(predicate: (element: TestValueClass) -> Boolean): TestValueClass =
+        TestValueClass(set.first { predicate(TestValueClass(it.toULong())) }.toULong())
+
+    /**
+     * Iterates over every element stored in this set by invoking
+     * the specified [block] lambda.
+     * @param block called with each element in the set
+     */
+    public inline fun forEach(block: (element: TestValueClass) -> Unit) {
+        contract { callsInPlace(block) }
+        set.forEach { block(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns true if all elements match the given [predicate].
+     * @param predicate called for elements in the set to determine if it returns return `true` for
+     * all elements.
+     */
+    public inline fun all(predicate: (element: TestValueClass) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        return set.all { predicate(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns true if at least one element matches the given [predicate].
+     * @param predicate called for elements in the set to determine if it returns `true` for any
+     * elements.
+     */
+    public inline fun any(predicate: (element: TestValueClass) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        return set.any { predicate(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns the number of elements in this set.
+     */
+    @androidx.annotation.IntRange(from = 0)
+    public inline fun count(): Int = set.count()
+
+    /**
+     * Returns the number of elements matching the given [predicate].
+     * @param predicate Called for all elements in the set to count the number for which it returns
+     * `true`.
+     */
+    @androidx.annotation.IntRange(from = 0)
+    public inline fun count(predicate: (element: TestValueClass) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        return set.count { predicate(TestValueClass(it.toULong())) }
+    }
+
+    /**
+     * Returns `true` if the specified [element] is present in this set, `false`
+     * otherwise.
+     * @param element The element to look for in this set
+     */
+    public inline operator fun contains(element: TestValueClass): Boolean =
+        set.contains(element.value.toLong())
+
+    /**
+     * Returns a string representation of this set. The set is denoted in the
+     * string by the `{}`. Each element is separated by `, `.
+     */
+    public override fun toString(): String = asTestValueClassSet().toString()
+
+    /**
+     * Creates a new [MutableTestValueClassSet]
+     * @param initialCapacity The initial desired capacity for this container.
+     * The container will honor this value by guaranteeing its internal structures
+     * can hold that many elements without requiring any allocations. The initial
+     * capacity can be set to 0.
+     */
+    public constructor(initialCapacity: Int = 6) : this(MutableLongSet(initialCapacity))
+
+    /**
+     * Returns a read-only interface to the set.
+     */
+    public inline fun asTestValueClassSet(): TestValueClassSet = TestValueClassSet(set)
+
+    /**
+     * Adds the specified element to the set.
+     * @param element The element to add to the set.
+     * @return `true` if the element has been added or `false` if the element is already
+     * contained within the set.
+     */
+    public inline fun add(element: TestValueClass): Boolean = set.add(element.value.toLong())
+
+    /**
+     * Adds the specified element to the set.
+     * @param element The element to add to the set.
+     */
+    public inline operator fun plusAssign(element: TestValueClass) =
+        set.plusAssign(element.value.toLong())
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     * @param elements A [TestValueClassSet] of elements to add to this set.
+     * @return `true` if any of the specified elements were added to the collection,
+     * `false` if the collection was not modified.
+     */
+    public inline fun addAll(elements: TestValueClassSet): Boolean = set.addAll(elements.set)
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     * @param elements A [TestValueClassSet] of elements to add to this set.
+     * @return `true` if any of the specified elements were added to the collection,
+     * `false` if the collection was not modified.
+     */
+    public inline fun addAll(elements: MutableTestValueClassSet): Boolean = set.addAll(elements.set)
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     * @param elements A [TestValueClassSet] of elements to add to this set.
+     */
+    public inline operator fun plusAssign(elements: TestValueClassSet) =
+        set.plusAssign(elements.set)
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     * @param elements A [TestValueClassSet] of elements to add to this set.
+     */
+    public inline operator fun plusAssign(elements: MutableTestValueClassSet) =
+        set.plusAssign(elements.set)
+
+    /**
+     * Removes the specified [element] from the set.
+     * @param element The element to remove from the set.
+     * @return `true` if the [element] was present in the set, or `false` if it wasn't
+     * present before removal.
+     */
+    public inline fun remove(element: TestValueClass): Boolean = set.remove(element.value.toLong())
+
+    /**
+     * Removes the specified [element] from the set if it is present.
+     * @param element The element to remove from the set.
+     */
+    public inline operator fun minusAssign(element: TestValueClass) =
+        set.minusAssign(element.value.toLong())
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     * @param elements An [TestValueClassSet] of elements to be removed from the set.
+     * @return `true` if the set was changed or `false` if none of the elements were present.
+     */
+    public inline fun removeAll(elements: TestValueClassSet): Boolean = set.removeAll(elements.set)
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     * @param elements An [TestValueClassSet] of elements to be removed from the set.
+     * @return `true` if the set was changed or `false` if none of the elements were present.
+     */
+    public inline fun removeAll(elements: MutableTestValueClassSet): Boolean =
+        set.removeAll(elements.set)
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     * @param elements An [TestValueClassSet] of elements to be removed from the set.
+     */
+    public inline operator fun minusAssign(elements: TestValueClassSet) =
+        set.minusAssign(elements.set)
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     * @param elements An [TestValueClassSet] of elements to be removed from the set.
+     */
+    public inline operator fun minusAssign(elements: MutableTestValueClassSet) =
+        set.minusAssign(elements.set)
+
+    /**
+     * Removes all elements from this set.
+     */
+    public inline fun clear() = set.clear()
+
+    /**
+     * Trims this [MutableTestValueClassSet]'s storage so it is sized appropriately
+     * to hold the current elements.
+     *
+     * Returns the number of empty elements removed from this set's storage.
+     * Returns 0 if no trimming is necessary or possible.
+     */
+    @androidx.annotation.IntRange(from = 0)
+    public inline fun trim(): Int = set.trim()
+}
diff --git a/collection/collection/template/ObjectPValueMap.kt.template b/collection/collection/template/ObjectPValueMap.kt.template
new file mode 100644
index 0000000..ba8284e
--- /dev/null
+++ b/collection/collection/template/ObjectPValueMap.kt.template
@@ -0,0 +1,855 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import androidx.collection.internal.EMPTY_OBJECTS
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyObjectPValueMap = MutableObjectPValueMap<Any?>(0)
+
+/**
+ * Returns an empty, read-only [ObjectPValueMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <K> emptyObjectPValueMap(): ObjectPValueMap<K> =
+    EmptyObjectPValueMap as ObjectPValueMap<K>
+
+/**
+ * Returns an empty, read-only [ObjectPValueMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <K> objectPValueMap(): ObjectPValueMap<K> =
+    EmptyObjectPValueMap as ObjectPValueMap<K>
+
+/**
+ * Returns a new [MutableObjectPValueMap].
+ */
+public fun <K> mutableObjectPValueMapOf(): MutableObjectPValueMap<K> = MutableObjectPValueMap()
+
+/**
+ * Returns a new [MutableObjectPValueMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * the [PValue] value is boxed. Use [set] for each entry instead when it is
+ * important to reduce allocations.
+ */
+public fun <K> objectPValueMapOf(vararg pairs: Pair<K, PValue>): ObjectPValueMap<K> =
+    MutableObjectPValueMap<K>(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutableObjectPValueMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * the [PValue] value is boxed. Use [set] for each entry instead when it is
+ * important to reduce allocations.
+ */
+public fun <K> mutableObjectPValueMapOf(vararg pairs: Pair<K, PValue>): MutableObjectPValueMap<K> =
+    MutableObjectPValueMap<K>(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [ObjectPValueMap] is a container with a [Map]-like interface for keys with
+ * reference types and [PValue] primitives for values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutableObjectPValueMap].
+ *
+ * @see [MutableObjectPValueMap]
+ * @see ScatterMap
+ */
+public sealed class ObjectPValueMap<K> {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: Array<Any?> = EMPTY_OBJECTS
+
+    @PublishedApi
+    @JvmField
+    internal var values: PValueArray = EmptyPValueArray
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key], or `null` if such
+     * a key is not present in the map.
+     * @throws NoSuchElementException when [key] is not found
+     */
+    public operator fun get(key: K): PValue {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            throw NoSuchElementException("There is no key $key in the map")
+        }
+        return values[index]
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: K, defaultValue: PValue): PValue {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: K, defaultValue: () -> PValue): PValue {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue()
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: K, value: PValue) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            block(k[index] as K, v[index])
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: K) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            block(k[index] as K)
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: PValue) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            block(v[index])
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (K, PValue) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (K, PValue) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (K, PValue) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: K): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: K): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: PValue): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [ObjectPValueMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is ObjectPValueMap<*>) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        @Suppress("UNCHECKED_CAST")
+        val o = other as ObjectPValueMap<Any?>
+
+        forEach { key, value ->
+            if (value != o[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(if (key === this) "(this)" else key)
+            s.append("=")
+            s.append(value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    @PublishedApi
+    internal fun findKeyIndex(key: K): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutableObjectPValueMap] is a container with a [MutableMap]-like interface for keys with
+ * reference types and [PValue] primitives for values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutableObjectPValueMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see MutableScatterMap
+ */
+public class MutableObjectPValueMap<K>(
+    initialCapacity: Int = DefaultScatterCapacity
+) : ObjectPValueMap<K>() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = arrayOfNulls(newCapacity)
+        values = PValueArray(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: K, defaultValue: () -> PValue): PValue {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        val value = defaultValue()
+        set(key, value)
+        return value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: K, value: PValue) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public fun put(key: K, value: PValue) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * the [PValue] value is boxed. Use [set] for each entry instead when it is
+     * important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<out Pair<K, PValue>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: ObjectPValueMap<K>) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that the [Pair] is allocated and the [PValue] value is boxed.
+     * Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<K, PValue>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * the [PValue] value is boxed. Use [set] for each entry instead when it is
+     * important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<out Pair<K, PValue>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: ObjectPValueMap<K>): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public fun remove(key: K) {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            removeValueAt(index)
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: K, value: PValue): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (K, PValue) -> Boolean) {
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            if (predicate(keys[index] as K, values[index])) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: K) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: Array<out K>) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: Iterable<K>) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: Sequence<K>) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: ScatterSet<K>) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int) {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+        keys[index] = null
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        keys.fill(null, 0, _capacity)
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: K): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutableObjectPValueMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/template/ObjectPValueMapTest.kt.template b/collection/collection/template/ObjectPValueMapTest.kt.template
new file mode 100644
index 0000000..d2c6f43
--- /dev/null
+++ b/collection/collection/template/ObjectPValueMapTest.kt.template
@@ -0,0 +1,630 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+@Suppress("RemoveRedundantCallsOfConversionMethods")
+class ObjectPValueTest {
+    @Test
+    fun objectPValueMap() {
+        val map = MutableObjectPValueMap<String>()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun emptyObjectPValueMap() {
+        val map = emptyObjectPValueMap<String>()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyObjectPValueMap<String>(), map)
+    }
+
+    @Test
+    fun objectPValueMapFunction() {
+        val map = mutableObjectPValueMapOf<String>()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutableObjectPValueMap<String>(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun objectPValueMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutableObjectPValueMap<String>(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun objectPValueMapPairsFunction() {
+        val map = mutableObjectPValueMapOf(
+            "Hello" to 1ValueSuffix,
+            "Bonjour" to 2ValueSuffix
+        )
+        assertEquals(2, map.size)
+        assertEquals(1ValueSuffix, map["Hello"])
+        assertEquals(2ValueSuffix, map["Bonjour"])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutableObjectPValueMap<String>()
+        map["Hello"] = 1ValueSuffix
+
+        assertEquals(1, map.size)
+        assertEquals(1ValueSuffix, map["Hello"])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutableObjectPValueMap<String>(12)
+        map["Hello"] = 1ValueSuffix
+
+        assertEquals(1, map.size)
+        assertEquals(1ValueSuffix, map["Hello"])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutableObjectPValueMap<String>(2)
+        map["Hello"] = 1ValueSuffix
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals(1ValueSuffix, map["Hello"])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutableObjectPValueMap<String>(0)
+        map["Hello"] = 1ValueSuffix
+
+        assertEquals(1, map.size)
+        assertEquals(1ValueSuffix, map["Hello"])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutableObjectPValueMap<String>()
+        map["Hello"] = 1ValueSuffix
+        map["Hello"] = 2ValueSuffix
+
+        assertEquals(1, map.size)
+        assertEquals(2ValueSuffix, map["Hello"])
+    }
+
+    @Test
+    fun put() {
+        val map = MutableObjectPValueMap<String>()
+
+        map.put("Hello", 1ValueSuffix)
+        assertEquals(1ValueSuffix, map["Hello"])
+        map.put("Hello", 2ValueSuffix)
+        assertEquals(2ValueSuffix, map["Hello"])
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutableObjectPValueMap<String?>()
+        map["Hello"] = 1ValueSuffix
+        map[null] = 2ValueSuffix
+        map["Bonjour"] = 2ValueSuffix
+
+        map.putAll(arrayOf("Hallo" to 3ValueSuffix, "Hola" to 7ValueSuffix))
+
+        assertEquals(5, map.size)
+        assertEquals(3ValueSuffix, map["Hallo"])
+        assertEquals(7ValueSuffix, map["Hola"])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutableObjectPValueMap<String>()
+        map += "Hello" to 1ValueSuffix
+
+        assertEquals(1, map.size)
+        assertEquals(1ValueSuffix, map["Hello"])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutableObjectPValueMap<String>()
+        map += arrayOf("Hallo" to 3ValueSuffix, "Hola" to 7ValueSuffix)
+
+        assertEquals(2, map.size)
+        assertEquals(3ValueSuffix, map["Hallo"])
+        assertEquals(7ValueSuffix, map["Hola"])
+    }
+
+    @Test
+    fun nullKey() {
+        val map = MutableObjectPValueMap<String?>()
+        map[null] = 1ValueSuffix
+
+        assertEquals(1, map.size)
+        assertEquals(1ValueSuffix, map[null])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutableObjectPValueMap<String>()
+        map["Hello"] = 1ValueSuffix
+
+        assertFailsWith<NoSuchElementException> {
+            map["Bonjour"]
+        }
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutableObjectPValueMap<String>()
+        map["Hello"] = 1ValueSuffix
+
+        assertEquals(2ValueSuffix, map.getOrDefault("Bonjour", 2ValueSuffix))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutableObjectPValueMap<String>()
+        map["Hello"] = 1ValueSuffix
+
+        assertEquals(3ValueSuffix, map.getOrElse("Hallo") { 3ValueSuffix })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutableObjectPValueMap<String>()
+        map["Hello"] = 1ValueSuffix
+
+        var counter = 0
+        map.getOrPut("Hello") {
+            counter++
+            2ValueSuffix
+        }
+        assertEquals(1ValueSuffix, map["Hello"])
+        assertEquals(0, counter)
+
+        map.getOrPut("Bonjour") {
+            counter++
+            2ValueSuffix
+        }
+        assertEquals(2ValueSuffix, map["Bonjour"])
+        assertEquals(1, counter)
+
+        map.getOrPut("Bonjour") {
+            counter++
+            3ValueSuffix
+        }
+        assertEquals(2ValueSuffix, map["Bonjour"])
+        assertEquals(1, counter)
+
+        map.getOrPut("Hallo") {
+            counter++
+            3ValueSuffix
+        }
+        assertEquals(3ValueSuffix, map["Hallo"])
+        assertEquals(2, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutableObjectPValueMap<String?>()
+        map.remove("Hello")
+
+        map["Hello"] = 1ValueSuffix
+        map.remove("Hello")
+        assertEquals(0, map.size)
+
+        map[null] = 1ValueSuffix
+        map.remove(null)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutableObjectPValueMap<String>(6)
+        map["Hello"] = 1ValueSuffix
+        map["Bonjour"] = 2ValueSuffix
+        map["Hallo"] = 3ValueSuffix
+        map["Konnichiwa"] = 4ValueSuffix
+        map["Ciao"] = 5ValueSuffix
+        map["Annyeong"] = 6ValueSuffix
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove("Hello")
+        map.remove("Bonjour")
+        map.remove("Hallo")
+        map.remove("Konnichiwa")
+        map.remove("Ciao")
+        map.remove("Annyeong")
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map["Hola"] = 7ValueSuffix
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutableObjectPValueMap<String>()
+        map["Hello"] = 1ValueSuffix
+        map["Bonjour"] = 2ValueSuffix
+        map["Hallo"] = 3ValueSuffix
+        map["Konnichiwa"] = 4ValueSuffix
+        map["Ciao"] = 5ValueSuffix
+        map["Annyeong"] = 6ValueSuffix
+
+        map.removeIf { key, _ -> key.startsWith('H') }
+
+        assertEquals(4, map.size)
+        assertEquals(2ValueSuffix, map["Bonjour"])
+        assertEquals(4ValueSuffix, map["Konnichiwa"])
+        assertEquals(5ValueSuffix, map["Ciao"])
+        assertEquals(6ValueSuffix, map["Annyeong"])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutableObjectPValueMap<String>()
+        map["Hello"] = 1ValueSuffix
+        map["Bonjour"] = 2ValueSuffix
+        map["Hallo"] = 3ValueSuffix
+
+        map -= "Hello"
+
+        assertEquals(2, map.size)
+        assertFalse("Hello" in map)
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutableObjectPValueMap<String>()
+        map["Hello"] = 1ValueSuffix
+        map["Bonjour"] = 2ValueSuffix
+        map["Hallo"] = 3ValueSuffix
+
+        map -= arrayOf("Hallo", "Bonjour")
+
+        assertEquals(1, map.size)
+        assertFalse("Hallo" in map)
+        assertFalse("Bonjour" in map)
+    }
+
+    @Test
+    fun minusIterable() {
+        val map = MutableObjectPValueMap<String>()
+        map["Hello"] = 1ValueSuffix
+        map["Bonjour"] = 2ValueSuffix
+        map["Hallo"] = 3ValueSuffix
+
+        map -= listOf("Hallo", "Bonjour")
+
+        assertEquals(1, map.size)
+        assertFalse("Hallo" in map)
+        assertFalse("Bonjour" in map)
+    }
+
+    @Test
+    fun minusSequence() {
+        val map = MutableObjectPValueMap<String>()
+        map["Hello"] = 1ValueSuffix
+        map["Bonjour"] = 2ValueSuffix
+        map["Hallo"] = 3ValueSuffix
+
+        map -= listOf("Hallo", "Bonjour").asSequence()
+
+        assertEquals(1, map.size)
+        assertFalse("Hallo" in map)
+        assertFalse("Bonjour" in map)
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutableObjectPValueMap<String?>()
+        assertFalse(map.remove("Hello", 1ValueSuffix))
+
+        map["Hello"] = 1ValueSuffix
+        assertTrue(map.remove("Hello", 1ValueSuffix))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutableObjectPValueMap<String>()
+
+        for (i in 0 until 1700) {
+            map[i.toString()] = i.toPValue()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutableObjectPValueMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toString()] = j.toPValue()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key, value.toInt().toString())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutableObjectPValueMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toString()] = j.toPValue()
+            }
+
+            var counter = 0
+            map.forEachKey { key ->
+                assertNotNull(key.toIntOrNull())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutableObjectPValueMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toString()] = j.toPValue()
+            }
+
+            var counter = 0
+            val values = BooleanArray(map.size)
+            map.forEachValue { value ->
+                values[value.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            values.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutableObjectPValueMap<String>()
+
+        for (i in 0 until 32) {
+            map[i.toString()] = i.toPValue()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutableObjectPValueMap<String?>()
+        assertEquals("{}", map.toString())
+
+        map["Hello"] = 1ValueSuffix
+        map["Bonjour"] = 2ValueSuffix
+        val oneString = 1ValueSuffix.toString()
+        val twoString = 2ValueSuffix.toString()
+        assertTrue(
+            "{Hello=$oneString, Bonjour=$twoString}" == map.toString() ||
+                "{Bonjour=$twoString, Hello=$oneString}" == map.toString()
+        )
+
+        map.clear()
+        map[null] = 2ValueSuffix
+        assertEquals("{null=$twoString}", map.toString())
+
+        val selfAsKeyMap = MutableObjectPValueMap<Any>()
+        selfAsKeyMap[selfAsKeyMap] = 1ValueSuffix
+        assertEquals("{(this)=$oneString}", selfAsKeyMap.toString())
+    }
+
+    @Test
+    fun equals() {
+        val map = MutableObjectPValueMap<String?>()
+        map["Hello"] = 1ValueSuffix
+        map[null] = 2ValueSuffix
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutableObjectPValueMap<String?>()
+        map2[null] = 2ValueSuffix
+
+        assertNotEquals(map, map2)
+
+        map2["Hello"] = 1ValueSuffix
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutableObjectPValueMap<String?>()
+        map["Hello"] = 1ValueSuffix
+        map[null] = 2ValueSuffix
+
+        assertTrue(map.containsKey("Hello"))
+        assertTrue(map.containsKey(null))
+        assertFalse(map.containsKey("Bonjour"))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutableObjectPValueMap<String?>()
+        map["Hello"] = 1ValueSuffix
+        map[null] = 2ValueSuffix
+
+        assertTrue("Hello" in map)
+        assertTrue(null in map)
+        assertFalse("Bonjour" in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutableObjectPValueMap<String?>()
+        map["Hello"] = 1ValueSuffix
+        map[null] = 2ValueSuffix
+
+        assertTrue(map.containsValue(1ValueSuffix))
+        assertTrue(map.containsValue(2ValueSuffix))
+        assertFalse(map.containsValue(3ValueSuffix))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutableObjectPValueMap<String?>()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map["Hello"] = 1ValueSuffix
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutableObjectPValueMap<String>()
+        assertEquals(0, map.count())
+
+        map["Hello"] = 1ValueSuffix
+        assertEquals(1, map.count())
+
+        map["Bonjour"] = 2ValueSuffix
+        map["Hallo"] = 3ValueSuffix
+        map["Konnichiwa"] = 4ValueSuffix
+        map["Ciao"] = 5ValueSuffix
+        map["Annyeong"] = 6ValueSuffix
+
+        assertEquals(2, map.count { key, _ -> key.startsWith("H") })
+        assertEquals(0, map.count { key, _ -> key.startsWith("W") })
+    }
+
+    @Test
+    fun any() {
+        val map = MutableObjectPValueMap<String>()
+        map["Hello"] = 1ValueSuffix
+        map["Bonjour"] = 2ValueSuffix
+        map["Hallo"] = 3ValueSuffix
+        map["Konnichiwa"] = 4ValueSuffix
+        map["Ciao"] = 5ValueSuffix
+        map["Annyeong"] = 6ValueSuffix
+
+        assertTrue(map.any { key, _ -> key.startsWith("K") })
+        assertFalse(map.any { key, _ -> key.startsWith("W") })
+    }
+
+    @Test
+    fun all() {
+        val map = MutableObjectPValueMap<String>()
+        map["Hello"] = 1ValueSuffix
+        map["Bonjour"] = 2ValueSuffix
+        map["Hallo"] = 3ValueSuffix
+        map["Konnichiwa"] = 4ValueSuffix
+        map["Ciao"] = 5ValueSuffix
+        map["Annyeong"] = 6ValueSuffix
+
+        assertTrue(map.all { key, value -> key.length >= 4 && value.toInt() >= 1 })
+        assertFalse(map.all { key, _ -> key.startsWith("W") })
+    }
+
+    @Test
+    fun trim() {
+        val map = MutableObjectPValueMap<String>()
+        assertEquals(7, map.trim())
+
+        map["Hello"] = 1ValueSuffix
+        map["Hallo"] = 3ValueSuffix
+
+        assertEquals(0, map.trim())
+
+        for (i in 0 until 1700) {
+            map[i.toString()] = i.toPValue()
+        }
+
+        assertEquals(2047, map.capacity)
+
+        // After removing these items, our capacity needs should go
+        // from 2047 down to 1023
+        for (i in 0 until 1700) {
+            if (i and 0x1 == 0x0) {
+                val s = i.toString()
+                map.remove(s)
+            }
+        }
+
+        assertEquals(1024, map.trim())
+        assertEquals(0, map.trim())
+    }
+}
diff --git a/collection/collection/template/PKeyList.kt.template b/collection/collection/template/PKeyList.kt.template
new file mode 100644
index 0000000..620d2e4
--- /dev/null
+++ b/collection/collection/template/PKeyList.kt.template
@@ -0,0 +1,922 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:Suppress("NOTHING_TO_INLINE", "RedundantVisibilityModifier")
+@file:OptIn(ExperimentalContracts::class)
+
+package androidx.collection
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+/**
+ * [PKeyList] is a [List]-like collection for [PKey] values. It allows retrieving
+ * the elements without boxing. [PKeyList] is always backed by a [MutablePKeyList],
+ * its [MutableList]-like subclass.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the list (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. It is also not safe to mutate during reentrancy --
+ * in the middle of a [forEach], for example. However, concurrent reads are safe.
+ */
+public sealed class PKeyList(initialCapacity: Int) {
+    @JvmField
+    @PublishedApi
+    internal var content: PKeyArray = if (initialCapacity == 0) {
+        EmptyPKeyArray
+    } else {
+        PKeyArray(initialCapacity)
+    }
+
+    @Suppress("PropertyName")
+    @JvmField
+    @PublishedApi
+    internal var _size: Int = 0
+
+    /**
+     * The number of elements in the [PKeyList].
+     */
+    @get:androidx.annotation.IntRange(from = 0)
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns the last valid index in the [PKeyList]. This can be `-1` when the list is empty.
+     */
+    @get:androidx.annotation.IntRange(from = -1)
+    public inline val lastIndex: Int get() = _size - 1
+
+    /**
+     * Returns an [IntRange] of the valid indices for this [PKeyList].
+     */
+    public inline val indices: IntRange get() = 0 until _size
+
+    /**
+     * Returns `true` if the collection has no elements in it.
+     */
+    public fun none(): Boolean {
+        return isEmpty()
+    }
+
+    /**
+     * Returns `true` if there's at least one element in the collection.
+     */
+    public fun any(): Boolean {
+        return isNotEmpty()
+    }
+
+    /**
+     * Returns `true` if any of the elements give a `true` return value for [predicate].
+     */
+    public inline fun any(predicate: (element: PKey) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        forEach {
+            if (predicate(it)) {
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Returns `true` if any of the elements give a `true` return value for [predicate] while
+     * iterating in the reverse order.
+     */
+    public inline fun reversedAny(predicate: (element: PKey) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        forEachReversed {
+            if (predicate(it)) {
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Returns `true` if the [PKeyList] contains [element] or `false` otherwise.
+     */
+    public operator fun contains(element: PKey): Boolean {
+        forEach {
+            if (it == element) {
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Returns `true` if the [PKeyList] contains all elements in [elements] or `false` if
+     * one or more are missing.
+     */
+    public fun containsAll(elements: PKeyList): Boolean {
+        for (i in elements.indices) {
+            if (!contains(elements[i])) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns the number of elements in this list.
+     */
+    public fun count(): Int = _size
+
+    /**
+     * Counts the number of elements matching [predicate].
+     * @return The number of elements in this list for which [predicate] returns true.
+     */
+    public inline fun count(predicate: (element: PKey) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        var count = 0
+        forEach { if (predicate(it)) count++ }
+        return count
+    }
+
+    /**
+     * Returns the first element in the [PKeyList] or throws a [NoSuchElementException] if
+     * it [isEmpty].
+     */
+    public fun first(): PKey {
+        if (isEmpty()) {
+            throw NoSuchElementException("PKeyList is empty.")
+        }
+        return content[0]
+    }
+
+    /**
+     * Returns the first element in the [PKeyList] for which [predicate] returns `true` or
+     * throws [NoSuchElementException] if nothing matches.
+     * @see indexOfFirst
+     */
+    public inline fun first(predicate: (element: PKey) -> Boolean): PKey {
+        contract { callsInPlace(predicate) }
+        forEach { item ->
+            if (predicate(item)) return item
+        }
+        throw NoSuchElementException("PKeyList contains no element matching the predicate.")
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [PKeyList] in order.
+     * @param initial The value of `acc` for the first call to [operation] or return value if
+     * there are no elements in this list.
+     * @param operation function that takes current accumulator value and an element, and
+     * calculates the next accumulator value.
+     */
+    public inline fun <R> fold(initial: R, operation: (acc: R, element: PKey) -> R): R {
+        contract { callsInPlace(operation) }
+        var acc = initial
+        forEach { item ->
+            acc = operation(acc, item)
+        }
+        return acc
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [PKeyList] in order.
+     */
+    public inline fun <R> foldIndexed(
+        initial: R,
+        operation: (index: Int, acc: R, element: PKey) -> R
+    ): R {
+        contract { callsInPlace(operation) }
+        var acc = initial
+        forEachIndexed { i, item ->
+            acc = operation(i, acc, item)
+        }
+        return acc
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [PKeyList] in reverse order.
+     * @param initial The value of `acc` for the first call to [operation] or return value if
+     * there are no elements in this list.
+     * @param operation function that takes an element and the current accumulator value, and
+     * calculates the next accumulator value.
+     */
+    public inline fun <R> foldRight(initial: R, operation: (element: PKey, acc: R) -> R): R {
+        contract { callsInPlace(operation) }
+        var acc = initial
+        forEachReversed { item ->
+            acc = operation(item, acc)
+        }
+        return acc
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [PKeyList] in reverse order.
+     */
+    public inline fun <R> foldRightIndexed(
+        initial: R,
+        operation: (index: Int, element: PKey, acc: R) -> R
+    ): R {
+        contract { callsInPlace(operation) }
+        var acc = initial
+        forEachReversedIndexed { i, item ->
+            acc = operation(i, item, acc)
+        }
+        return acc
+    }
+
+    /**
+     * Calls [block] for each element in the [PKeyList], in order.
+     * @param block will be executed for every element in the list, accepting an element from
+     * the list
+     */
+    public inline fun forEach(block: (element: PKey) -> Unit) {
+        contract { callsInPlace(block) }
+        val content = content
+        for (i in 0 until _size) {
+            block(content[i])
+        }
+    }
+
+    /**
+     * Calls [block] for each element in the [PKeyList] along with its index, in order.
+     * @param block will be executed for every element in the list, accepting the index and
+     * the element at that index.
+     */
+    public inline fun forEachIndexed(block: (index: Int, element: PKey) -> Unit) {
+        contract { callsInPlace(block) }
+        val content = content
+        for (i in 0 until _size) {
+            block(i, content[i])
+        }
+    }
+
+    /**
+     * Calls [block] for each element in the [PKeyList] in reverse order.
+     * @param block will be executed for every element in the list, accepting an element from
+     * the list
+     */
+    public inline fun forEachReversed(block: (element: PKey) -> Unit) {
+        contract { callsInPlace(block) }
+        val content = content
+        for (i in _size - 1 downTo 0) {
+            block(content[i])
+        }
+    }
+
+    /**
+     * Calls [block] for each element in the [PKeyList] along with its index, in reverse
+     * order.
+     * @param block will be executed for every element in the list, accepting the index and
+     * the element at that index.
+     */
+    public inline fun forEachReversedIndexed(block: (index: Int, element: PKey) -> Unit) {
+        contract { callsInPlace(block) }
+        val content = content
+        for (i in _size - 1 downTo 0) {
+            block(i, content[i])
+        }
+    }
+
+    /**
+     * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if
+     * the [index] is out of bounds of this collection.
+     */
+    public operator fun get(@androidx.annotation.IntRange(from = 0) index: Int): PKey {
+        if (index !in 0 until _size) {
+            throw IndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+        }
+        return content[index]
+    }
+
+    /**
+     * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if
+     * the [index] is out of bounds of this collection.
+     */
+    public fun elementAt(@androidx.annotation.IntRange(from = 0) index: Int): PKey {
+        if (index !in 0 until _size) {
+            throw IndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+        }
+        return content[index]
+    }
+
+    /**
+     * Returns the element at the given [index] or [defaultValue] if [index] is out of bounds
+     * of the collection.
+     * @param index The index of the element whose value should be returned
+     * @param defaultValue A lambda to call with [index] as a parameter to return a value at
+     * an index not in the list.
+     */
+    public inline fun elementAtOrElse(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        defaultValue: (index: Int) -> PKey
+    ): PKey {
+        if (index !in 0 until _size) {
+            return defaultValue(index)
+        }
+        return content[index]
+    }
+
+    /**
+     * Returns the index of [element] in the [PKeyList] or `-1` if [element] is not there.
+     */
+    public fun indexOf(element: PKey): Int {
+        forEachIndexed { i, item ->
+            if (element == item) {
+                return i
+            }
+        }
+        return -1
+    }
+
+    /**
+     * Returns the index if the first element in the [PKeyList] for which [predicate]
+     * returns `true`.
+     */
+    public inline fun indexOfFirst(predicate: (element: PKey) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        forEachIndexed { i, item ->
+            if (predicate(item)) {
+                return i
+            }
+        }
+        return -1
+    }
+
+    /**
+     * Returns the index if the last element in the [PKeyList] for which [predicate]
+     * returns `true`.
+     */
+    public inline fun indexOfLast(predicate: (element: PKey) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        forEachReversedIndexed { i, item ->
+            if (predicate(item)) {
+                return i
+            }
+        }
+        return -1
+    }
+
+    /**
+     * Returns `true` if the [PKeyList] has no elements in it or `false` otherwise.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if there are elements in the [PKeyList] or `false` if it is empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the last element in the [PKeyList] or throws a [NoSuchElementException] if
+     * it [isEmpty].
+     */
+    public fun last(): PKey {
+        if (isEmpty()) {
+            throw NoSuchElementException("PKeyList is empty.")
+        }
+        return content[lastIndex]
+    }
+
+    /**
+     * Returns the last element in the [PKeyList] for which [predicate] returns `true` or
+     * throws [NoSuchElementException] if nothing matches.
+     * @see indexOfLast
+     */
+    public inline fun last(predicate: (element: PKey) -> Boolean): PKey {
+        contract { callsInPlace(predicate) }
+        forEachReversed { item ->
+            if (predicate(item)) {
+                return item
+            }
+        }
+        throw NoSuchElementException("PKeyList contains no element matching the predicate.")
+    }
+
+    /**
+     * Returns the index of the last element in the [PKeyList] that is the same as
+     * [element] or `-1` if no elements match.
+     */
+    public fun lastIndexOf(element: PKey): Int {
+        forEachReversedIndexed { i, item ->
+            if (item == element) {
+                return i
+            }
+        }
+        return -1
+    }
+
+    /**
+     * Returns a hash code based on the contents of the [PKeyList].
+     */
+    override fun hashCode(): Int {
+        var hashCode = 0
+        forEach { element ->
+            hashCode += 31 * element.hashCode()
+        }
+        return hashCode
+    }
+
+    /**
+     * Returns `true` if [other] is a [PKeyList] and the contents of this and [other] are the
+     * same.
+     */
+    override fun equals(other: Any?): Boolean {
+        if (other !is PKeyList || other._size != _size) {
+            return false
+        }
+        val content = content
+        val otherContent = other.content
+        for (i in indices) {
+            if (content[i] != otherContent[i]) {
+                return false
+            }
+        }
+        return true
+    }
+
+    /**
+     * Returns a String representation of the list, surrounded by "[]" and each element
+     * separated by ", ".
+     */
+    override fun toString(): String {
+        if (isEmpty()) {
+            return "[]"
+        }
+        val last = lastIndex
+        return buildString {
+            append('[')
+            val content = content
+            for (i in 0 until last) {
+                append(content[i])
+                append(',')
+                append(' ')
+            }
+            append(content[last])
+            append(']')
+        }
+    }
+}
+
+/**
+ * [MutablePKeyList] is a [MutableList]-like collection for [PKey] values.
+ * It allows storing and retrieving the elements without boxing. Immutable
+ * access is available through its base class [PKeyList], which has a [List]-like
+ * interface.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the list (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. It is also not safe to mutate during reentrancy --
+ * in the middle of a [forEach], for example. However, concurrent reads are safe.
+ *
+ * @constructor Creates a [MutablePKeyList] with a [capacity] of `initialCapacity`.
+ */
+public class MutablePKeyList(
+    initialCapacity: Int = 16
+) : PKeyList(initialCapacity) {
+    /**
+     * Returns the total number of elements that can be held before the [MutablePKeyList] must
+     * grow.
+     *
+     * @see ensureCapacity
+     */
+    public inline val capacity: Int
+        get() = content.size
+
+    /**
+     * Adds [element] to the [MutablePKeyList] and returns `true`.
+     */
+    public fun add(element: PKey): Boolean {
+        ensureCapacity(_size + 1)
+        content[_size] = element
+        _size++
+        return true
+    }
+
+    /**
+     * Adds [element] to the [MutablePKeyList] at the given [index], shifting over any
+     * elements at [index] and after, if any.
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
+     */
+    public fun add(@androidx.annotation.IntRange(from = 0) index: Int, element: PKey) {
+        if (index !in 0.._size) {
+            throw IndexOutOfBoundsException("Index $index must be in 0..$_size")
+        }
+        ensureCapacity(_size + 1)
+        val content = content
+        if (index != _size) {
+            content.copyInto(
+                destination = content,
+                destinationOffset = index + 1,
+                startIndex = index,
+                endIndex = _size
+            )
+        }
+        content[index] = element
+        _size++
+    }
+
+    /**
+     * Adds all [elements] to the [MutablePKeyList] at the given [index], shifting over any
+     * elements at [index] and after, if any.
+     * @return `true` if the [MutablePKeyList] was changed or `false` if [elements] was empty
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive.
+     */
+    public fun addAll(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        elements: PKeyArray
+    ): Boolean {
+        if (index !in 0.._size) {
+            throw IndexOutOfBoundsException("Index $index must be in 0..$_size")
+        }
+        if (elements.isEmpty()) return false
+        ensureCapacity(_size + elements.size)
+        val content = content
+        if (index != _size) {
+            content.copyInto(
+                destination = content,
+                destinationOffset = index + elements.size,
+                startIndex = index,
+                endIndex = _size
+            )
+        }
+        elements.copyInto(content, index)
+        _size += elements.size
+        return true
+    }
+
+    /**
+     * Adds all [elements] to the [MutablePKeyList] at the given [index], shifting over any
+     * elements at [index] and after, if any.
+     * @return `true` if the [MutablePKeyList] was changed or `false` if [elements] was empty
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
+     */
+    public fun addAll(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        elements: PKeyList
+    ): Boolean {
+        if (index !in 0.._size) {
+            throw IndexOutOfBoundsException("Index $index must be in 0..$_size")
+        }
+        if (elements.isEmpty()) return false
+        ensureCapacity(_size + elements._size)
+        val content = content
+        if (index != _size) {
+            content.copyInto(
+                destination = content,
+                destinationOffset = index + elements._size,
+                startIndex = index,
+                endIndex = _size
+            )
+        }
+        elements.content.copyInto(
+            destination = content,
+            destinationOffset = index,
+            startIndex = 0,
+            endIndex = elements._size
+        )
+        _size += elements._size
+        return true
+    }
+
+    /**
+     * Adds all [elements] to the end of the [MutablePKeyList] and returns `true` if the
+     * [MutablePKeyList] was changed or `false` if [elements] was empty.
+     */
+    public fun addAll(elements: PKeyList): Boolean {
+        return addAll(_size, elements)
+    }
+
+    /**
+     * Adds all [elements] to the end of the [MutablePKeyList] and returns `true` if the
+     * [MutablePKeyList] was changed or `false` if [elements] was empty.
+     */
+    public fun addAll(elements: PKeyArray): Boolean {
+        return addAll(_size, elements)
+    }
+
+    /**
+     * Adds all [elements] to the end of the [MutablePKeyList].
+     */
+    public operator fun plusAssign(elements: PKeyList) {
+        addAll(_size, elements)
+    }
+
+    /**
+     * Adds all [elements] to the end of the [MutablePKeyList].
+     */
+    public operator fun plusAssign(elements: PKeyArray) {
+        addAll(_size, elements)
+    }
+
+    /**
+     * Removes all elements in the [MutablePKeyList]. The storage isn't released.
+     * @see trim
+     */
+    public fun clear() {
+        _size = 0
+    }
+
+    /**
+     * Reduces the internal storage. If [capacity] is greater than [minCapacity] and [size], the
+     * internal storage is reduced to the maximum of [size] and [minCapacity].
+     * @see ensureCapacity
+     */
+    public fun trim(minCapacity: Int = _size) {
+        val minSize = maxOf(minCapacity, _size)
+        if (capacity > minSize) {
+            content = content.copyOf(minSize)
+        }
+    }
+
+    /**
+     * Ensures that there is enough space to store [capacity] elements in the [MutablePKeyList].
+     * @see trim
+     */
+    public fun ensureCapacity(capacity: Int) {
+        val oldContent = content
+        if (oldContent.size < capacity) {
+            val newSize = maxOf(capacity, oldContent.size * 3 / 2)
+            content = oldContent.copyOf(newSize)
+        }
+    }
+
+    /**
+     * [add] [element] to the [MutablePKeyList].
+     */
+    public inline operator fun plusAssign(element: PKey) {
+        add(element)
+    }
+
+    /**
+     * [remove] [element] from the [MutablePKeyList]
+     */
+    public inline operator fun minusAssign(element: PKey) {
+        remove(element)
+    }
+
+    /**
+     * Removes [element] from the [MutablePKeyList]. If [element] was in the [MutablePKeyList]
+     * and was removed, `true` will be returned, or `false` will be returned if the element
+     * was not found.
+     */
+    public fun remove(element: PKey): Boolean {
+        val index = indexOf(element)
+        if (index >= 0) {
+            removeAt(index)
+            return true
+        }
+        return false
+    }
+
+    /**
+     * Removes all [elements] from the [MutablePKeyList] and returns `true` if anything was removed.
+     */
+    public fun removeAll(elements: PKeyArray): Boolean {
+        val initialSize = _size
+        for (i in elements.indices) {
+            remove(elements[i])
+        }
+        return initialSize != _size
+    }
+
+    /**
+     * Removes all [elements] from the [MutablePKeyList] and returns `true` if anything was removed.
+     */
+    public fun removeAll(elements: PKeyList): Boolean {
+        val initialSize = _size
+        for (i in 0..elements.lastIndex) {
+            remove(elements[i])
+        }
+        return initialSize != _size
+    }
+
+    /**
+     * Removes all [elements] from the [MutablePKeyList].
+     */
+    public operator fun minusAssign(elements: PKeyArray) {
+        elements.forEach { element ->
+            remove(element)
+        }
+    }
+
+    /**
+     * Removes all [elements] from the [MutablePKeyList].
+     */
+    public operator fun minusAssign(elements: PKeyList) {
+        elements.forEach { element ->
+            remove(element)
+        }
+    }
+
+    /**
+     * Removes the element at the given [index] and returns it.
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [lastIndex], inclusive
+     */
+    public fun removeAt(@androidx.annotation.IntRange(from = 0) index: Int): PKey {
+        if (index !in 0 until _size) {
+            throw IndexOutOfBoundsException("Index $index must be in 0..$lastIndex")
+        }
+        val content = content
+        val item = content[index]
+        if (index != lastIndex) {
+            content.copyInto(
+                destination = content,
+                destinationOffset = index,
+                startIndex = index + 1,
+                endIndex = _size
+            )
+        }
+        _size--
+        return item
+    }
+
+    /**
+     * Removes items from index [start] (inclusive) to [end] (exclusive).
+     * @throws IndexOutOfBoundsException if [start] or [end] isn't between 0 and [size], inclusive
+     * @throws IllegalArgumentException if [start] is greater than [end]
+     */
+    public fun removeRange(
+        @androidx.annotation.IntRange(from = 0) start: Int,
+        @androidx.annotation.IntRange(from = 0) end: Int
+    ) {
+        if (start !in 0.._size || end !in 0.._size) {
+            throw IndexOutOfBoundsException("Start ($start) and end ($end) must be in 0..$_size")
+        }
+        if (end < start) {
+            throw IllegalArgumentException("Start ($start) is more than end ($end)")
+        }
+        if (end != start) {
+            if (end < _size) {
+                content.copyInto(
+                    destination = content,
+                    destinationOffset = start,
+                    startIndex = end,
+                    endIndex = _size
+                )
+            }
+            _size -= (end - start)
+        }
+    }
+
+    /**
+     * Keeps only [elements] in the [MutablePKeyList] and removes all other values.
+     * @return `true` if the [MutablePKeyList] has changed.
+     */
+    public fun retainAll(elements: PKeyArray): Boolean {
+        val initialSize = _size
+        val content = content
+        for (i in lastIndex downTo 0) {
+            val item = content[i]
+            if (elements.indexOfFirst { it == item } < 0) {
+                removeAt(i)
+            }
+        }
+        return initialSize != _size
+    }
+
+    /**
+     * Keeps only [elements] in the [MutablePKeyList] and removes all other values.
+     * @return `true` if the [MutablePKeyList] has changed.
+     */
+    public fun retainAll(elements: PKeyList): Boolean {
+        val initialSize = _size
+        val content = content
+        for (i in lastIndex downTo 0) {
+            val item = content[i]
+            if (item !in elements) {
+                removeAt(i)
+            }
+        }
+        return initialSize != _size
+    }
+
+    /**
+     * Sets the value at [index] to [element].
+     * @return the previous value set at [index]
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [lastIndex], inclusive
+     */
+    public operator fun set(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        element: PKey
+    ): PKey {
+        if (index !in 0 until _size) {
+            throw IndexOutOfBoundsException("set index $index must be between 0 .. $lastIndex")
+        }
+        val content = content
+        val old = content[index]
+        content[index] = element
+        return old
+    }
+
+    /**
+     * Sorts the [MutablePKeyList] elements in ascending order.
+     */
+    public fun sort() {
+        content.sort(fromIndex = 0, toIndex = _size)
+    }
+
+    /**
+     * Sorts the [MutablePKeyList] elements in descending order.
+     */
+    public fun sortDescending() {
+        content.sortDescending(fromIndex = 0, toIndex = _size)
+    }
+}
+
+private val EmptyPKeyList: PKeyList = MutablePKeyList(0)
+
+/**
+ * @return a read-only [PKeyList] with nothing in it.
+ */
+public fun emptyPKeyList(): PKeyList = EmptyPKeyList
+
+/**
+ * @return a read-only [PKeyList] with nothing in it.
+ */
+public fun pKeyListOf(): PKeyList = EmptyPKeyList
+
+/**
+ * @return a new read-only [PKeyList] with [element1] as the only item in the list.
+ */
+public fun pKeyListOf(element1: PKey): PKeyList = mutablePKeyListOf(element1)
+
+/**
+ * @return a new read-only [PKeyList] with 2 elements, [element1] and [element2], in order.
+ */
+public fun pKeyListOf(element1: PKey, element2: PKey): PKeyList =
+    mutablePKeyListOf(element1, element2)
+
+/**
+ * @return a new read-only [PKeyList] with 3 elements, [element1], [element2], and [element3],
+ * in order.
+ */
+public fun pKeyListOf(element1: PKey, element2: PKey, element3: PKey): PKeyList =
+    mutablePKeyListOf(element1, element2, element3)
+
+/**
+ * @return a new read-only [PKeyList] with [elements] in order.
+ */
+public fun pKeyListOf(vararg elements: PKey): PKeyList =
+    MutablePKeyList(elements.size).apply { plusAssign(elements) }
+
+/**
+ * @return a new empty [MutablePKeyList] with the default capacity.
+ */
+public inline fun mutablePKeyListOf(): MutablePKeyList = MutablePKeyList()
+
+/**
+ * @return a new [MutablePKeyList] with [element1] as the only item in the list.
+ */
+public fun mutablePKeyListOf(element1: PKey): MutablePKeyList {
+    val list = MutablePKeyList(1)
+    list += element1
+    return list
+}
+
+/**
+ * @return a new [MutablePKeyList] with 2 elements, [element1] and [element2], in order.
+ */
+public fun mutablePKeyListOf(element1: PKey, element2: PKey): MutablePKeyList {
+    val list = MutablePKeyList(2)
+    list += element1
+    list += element2
+    return list
+}
+
+/**
+ * @return a new [MutablePKeyList] with 3 elements, [element1], [element2], and [element3],
+ * in order.
+ */
+public fun mutablePKeyListOf(element1: PKey, element2: PKey, element3: PKey): MutablePKeyList {
+    val list = MutablePKeyList(3)
+    list += element1
+    list += element2
+    list += element3
+    return list
+}
+
+/**
+ * @return a new [MutablePKeyList] with the given elements, in order.
+ */
+public inline fun mutablePKeyListOf(vararg elements: PKey): MutablePKeyList =
+    MutablePKeyList(elements.size).apply { plusAssign(elements) }
diff --git a/collection/collection/template/PKeyListTest.kt.template b/collection/collection/template/PKeyListTest.kt.template
new file mode 100644
index 0000000..34915e5
--- /dev/null
+++ b/collection/collection/template/PKeyListTest.kt.template
@@ -0,0 +1,723 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+class PKeyListTest {
+    private val list: MutablePKeyList = mutablePKeyListOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix)
+
+    @Test
+    fun emptyConstruction() {
+        val l = mutablePKeyListOf()
+        assertEquals(0, l.size)
+        assertEquals(16, l.capacity)
+    }
+
+    @Test
+    fun sizeConstruction() {
+        val l = MutablePKeyList(4)
+        assertEquals(4, l.capacity)
+    }
+
+    @Test
+    fun contentConstruction() {
+        val l = mutablePKeyListOf(1KeySuffix, 2KeySuffix, 3KeySuffix)
+        assertEquals(3, l.size)
+        assertEquals(1KeySuffix, l[0])
+        assertEquals(2KeySuffix, l[1])
+        assertEquals(3KeySuffix, l[2])
+        assertEquals(3, l.capacity)
+        repeat(2) {
+            val l2 = mutablePKeyListOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix)
+            assertEquals(list, l2)
+            l2.removeAt(0)
+        }
+    }
+
+    @Test
+    fun hashCodeTest() {
+        val l2 = mutablePKeyListOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix)
+        assertEquals(list.hashCode(), l2.hashCode())
+        l2.removeAt(4)
+        assertNotEquals(list.hashCode(), l2.hashCode())
+        l2.add(5KeySuffix)
+        assertEquals(list.hashCode(), l2.hashCode())
+        l2.clear()
+        assertNotEquals(list.hashCode(), l2.hashCode())
+    }
+
+    @Test
+    fun equalsTest() {
+        val l2 = mutablePKeyListOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix)
+        assertEquals(list, l2)
+        assertNotEquals(list, mutablePKeyListOf())
+        l2.removeAt(4)
+        assertNotEquals(list, l2)
+        l2.add(5KeySuffix)
+        assertEquals(list, l2)
+        l2.clear()
+        assertNotEquals(list, l2)
+    }
+
+    @Test
+    fun string() {
+        assertEquals("[${1KeySuffix}, ${2KeySuffix}, ${3KeySuffix}, ${4KeySuffix}, ${5KeySuffix}]", list.toString())
+        assertEquals("[]", mutablePKeyListOf().toString())
+    }
+
+    @Test
+    fun size() {
+        assertEquals(5, list.size)
+        assertEquals(5, list.count())
+        val l2 = mutablePKeyListOf()
+        assertEquals(0, l2.size)
+        assertEquals(0, l2.count())
+        l2 += 1KeySuffix
+        assertEquals(1, l2.size)
+        assertEquals(1, l2.count())
+    }
+
+    @Test
+    fun get() {
+        assertEquals(1KeySuffix, list[0])
+        assertEquals(5KeySuffix, list[4])
+        assertEquals(1KeySuffix, list.elementAt(0))
+        assertEquals(5KeySuffix, list.elementAt(4))
+    }
+
+    @Test
+    fun getOutOfBounds() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            list[5]
+        }
+    }
+
+    @Test
+    fun getOutOfBoundsNegative() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            list[-1]
+        }
+    }
+
+    @Test
+    fun elementAtOfBounds() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            list.elementAt(5)
+        }
+    }
+
+    @Test
+    fun elementAtOfBoundsNegative() {
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            list.elementAt(-1)
+        }
+    }
+
+    @Test
+    fun elementAtOrElse() {
+        assertEquals(1KeySuffix, list.elementAtOrElse(0) {
+            assertEquals(0, it)
+            0KeySuffix
+        })
+        assertEquals(0KeySuffix, list.elementAtOrElse(-1) {
+            assertEquals(-1, it)
+            0KeySuffix
+        })
+        assertEquals(0KeySuffix, list.elementAtOrElse(5) {
+            assertEquals(5, it)
+            0KeySuffix
+        })
+    }
+
+    @Test
+    fun count() {
+        assertEquals(1, list.count { it < 2KeySuffix })
+        assertEquals(0, list.count { it < 0KeySuffix })
+        assertEquals(5, list.count { it < 10KeySuffix })
+    }
+
+    @Test
+    fun isEmpty() {
+        assertFalse(list.isEmpty())
+        assertFalse(list.none())
+        assertTrue(mutablePKeyListOf().isEmpty())
+        assertTrue(mutablePKeyListOf().none())
+    }
+
+    @Test
+    fun isNotEmpty() {
+        assertTrue(list.isNotEmpty())
+        assertTrue(list.any())
+        assertFalse(mutablePKeyListOf().isNotEmpty())
+    }
+
+    @Test
+    fun indices() {
+        assertEquals(IntRange(0, 4), list.indices)
+        assertEquals(IntRange(0, -1), mutablePKeyListOf().indices)
+    }
+
+    @Test
+    fun any() {
+        assertTrue(list.any { it == 5KeySuffix })
+        assertTrue(list.any { it == 1KeySuffix })
+        assertFalse(list.any { it == 0KeySuffix })
+    }
+
+    @Test
+    fun reversedAny() {
+        val reversedList = mutablePKeyListOf()
+        assertFalse(
+            list.reversedAny {
+                reversedList.add(it)
+                false
+            }
+        )
+        val reversedContent = mutablePKeyListOf(5KeySuffix, 4KeySuffix, 3KeySuffix, 2KeySuffix, 1KeySuffix)
+        assertEquals(reversedContent, reversedList)
+
+        val reversedSublist = mutablePKeyListOf()
+        assertTrue(
+            list.reversedAny {
+                reversedSublist.add(it)
+                reversedSublist.size == 2
+            }
+        )
+        assertEquals(reversedSublist, mutablePKeyListOf(5KeySuffix, 4KeySuffix))
+    }
+
+    @Test
+    fun forEach() {
+        val copy = mutablePKeyListOf()
+        list.forEach { copy += it }
+        assertEquals(list, copy)
+    }
+
+    @Test
+    fun forEachReversed() {
+        val copy = mutablePKeyListOf()
+        list.forEachReversed { copy += it }
+        assertEquals(copy, mutablePKeyListOf(5KeySuffix, 4KeySuffix, 3KeySuffix, 2KeySuffix, 1KeySuffix))
+    }
+
+    @Test
+    fun forEachIndexed() {
+        val copy = mutablePKeyListOf()
+        val indices = mutablePKeyListOf()
+        list.forEachIndexed { index, item ->
+            copy += item
+            indices += index.toPKey()
+        }
+        assertEquals(list, copy)
+        assertEquals(indices, mutablePKeyListOf(0KeySuffix, 1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix))
+    }
+
+    @Test
+    fun forEachReversedIndexed() {
+        val copy = mutablePKeyListOf()
+        val indices = mutablePKeyListOf()
+        list.forEachReversedIndexed { index, item ->
+            copy += item
+            indices += index.toPKey()
+        }
+        assertEquals(copy, mutablePKeyListOf(5KeySuffix, 4KeySuffix, 3KeySuffix, 2KeySuffix, 1KeySuffix))
+        assertEquals(indices, mutablePKeyListOf(4KeySuffix, 3KeySuffix, 2KeySuffix, 1KeySuffix, 0KeySuffix))
+    }
+
+    @Test
+    fun indexOfFirst() {
+        assertEquals(0, list.indexOfFirst { it == 1KeySuffix })
+        assertEquals(4, list.indexOfFirst { it == 5KeySuffix })
+        assertEquals(-1, list.indexOfFirst { it == 0KeySuffix })
+        assertEquals(0, mutablePKeyListOf(8KeySuffix, 8KeySuffix).indexOfFirst { it == 8KeySuffix })
+    }
+
+    @Test
+    fun indexOfLast() {
+        assertEquals(0, list.indexOfLast { it == 1KeySuffix })
+        assertEquals(4, list.indexOfLast { it == 5KeySuffix })
+        assertEquals(-1, list.indexOfLast { it == 0KeySuffix })
+        assertEquals(1, mutablePKeyListOf(8KeySuffix, 8KeySuffix).indexOfLast { it == 8KeySuffix })
+    }
+
+    @Test
+    fun contains() {
+        assertTrue(list.contains(5KeySuffix))
+        assertTrue(list.contains(1KeySuffix))
+        assertFalse(list.contains(0KeySuffix))
+    }
+
+    @Test
+    fun containsAllList() {
+        assertTrue(list.containsAll(mutablePKeyListOf(2KeySuffix, 3KeySuffix, 1KeySuffix)))
+        assertFalse(list.containsAll(mutablePKeyListOf(2KeySuffix, 3KeySuffix, 6KeySuffix)))
+    }
+
+    @Test
+    fun lastIndexOf() {
+        assertEquals(4, list.lastIndexOf(5KeySuffix))
+        assertEquals(1, list.lastIndexOf(2KeySuffix))
+        val copy = mutablePKeyListOf()
+        copy.addAll(list)
+        copy.addAll(list)
+        assertEquals(5, copy.lastIndexOf(1KeySuffix))
+    }
+
+    @Test
+    fun first() {
+        assertEquals(1KeySuffix, list.first())
+    }
+
+    @Test
+    fun firstException() {
+        assertFailsWith(NoSuchElementException::class) {
+            mutablePKeyListOf().first()
+        }
+    }
+
+    @Test
+    fun firstWithPredicate() {
+        assertEquals(5KeySuffix, list.first { it == 5KeySuffix })
+        assertEquals(1KeySuffix, mutablePKeyListOf(1KeySuffix, 5KeySuffix).first { it != 0KeySuffix })
+    }
+
+    @Test
+    fun firstWithPredicateException() {
+        assertFailsWith(NoSuchElementException::class) {
+            mutablePKeyListOf().first { it == 8KeySuffix }
+        }
+    }
+
+    @Test
+    fun last() {
+        assertEquals(5KeySuffix, list.last())
+    }
+
+    @Test
+    fun lastException() {
+        assertFailsWith(NoSuchElementException::class) {
+            mutablePKeyListOf().last()
+        }
+    }
+
+    @Test
+    fun lastWithPredicate() {
+        assertEquals(1KeySuffix, list.last { it == 1KeySuffix })
+        assertEquals(5KeySuffix, mutablePKeyListOf(1KeySuffix, 5KeySuffix).last { it != 0KeySuffix })
+    }
+
+    @Test
+    fun lastWithPredicateException() {
+        assertFailsWith(NoSuchElementException::class) {
+            mutablePKeyListOf().last { it == 8KeySuffix }
+        }
+    }
+
+    @Test
+    fun fold() {
+        assertEquals("12345", list.fold("") { acc, i -> acc + i.toInt().toString() })
+    }
+
+    @Test
+    fun foldIndexed() {
+        assertEquals(
+            "01-12-23-34-45-",
+            list.foldIndexed("") { index, acc, i ->
+                "$acc$index${i.toInt()}-"
+            }
+        )
+    }
+
+    @Test
+    fun foldRight() {
+        assertEquals("54321", list.foldRight("") { i, acc -> acc + i.toInt().toString() })
+    }
+
+    @Test
+    fun foldRightIndexed() {
+        assertEquals(
+            "45-34-23-12-01-",
+            list.foldRightIndexed("") { index, i, acc ->
+                "$acc$index${i.toInt()}-"
+            }
+        )
+    }
+
+    @Test
+    fun add() {
+        val l = mutablePKeyListOf(1KeySuffix, 2KeySuffix, 3KeySuffix)
+        l += 4KeySuffix
+        l.add(5KeySuffix)
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun addAtIndex() {
+        val l = mutablePKeyListOf(2KeySuffix, 4KeySuffix)
+        l.add(2, 5KeySuffix)
+        l.add(0, 1KeySuffix)
+        l.add(2, 3KeySuffix)
+        assertEquals(list, l)
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l.add(-1, 2KeySuffix)
+        }
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l.add(6, 2KeySuffix)
+        }
+    }
+
+    @Test
+    fun addAllListAtIndex() {
+        val l = mutablePKeyListOf(4KeySuffix)
+        val l2 = mutablePKeyListOf(1KeySuffix, 2KeySuffix)
+        val l3 = mutablePKeyListOf(5KeySuffix)
+        val l4 = mutablePKeyListOf(3KeySuffix)
+        assertTrue(l4.addAll(1, l3))
+        assertTrue(l4.addAll(0, l2))
+        assertTrue(l4.addAll(3, l))
+        assertFalse(l4.addAll(0, mutablePKeyListOf()))
+        assertEquals(list, l4)
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l4.addAll(6, mutablePKeyListOf())
+        }
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l4.addAll(-1, mutablePKeyListOf())
+        }
+    }
+
+    @Test
+    fun addAllList() {
+        val l = MutablePKeyList()
+        l.add(3KeySuffix)
+        l.add(4KeySuffix)
+        l.add(5KeySuffix)
+        val l2 = mutablePKeyListOf(1KeySuffix, 2KeySuffix)
+        assertTrue(l2.addAll(l))
+        assertEquals(list, l2)
+        assertFalse(l2.addAll(mutablePKeyListOf()))
+    }
+
+    @Test
+    fun plusAssignList() {
+        val l = MutablePKeyList()
+        l.add(3KeySuffix)
+        l.add(4KeySuffix)
+        l.add(5KeySuffix)
+        val l2 = mutablePKeyListOf(1KeySuffix, 2KeySuffix)
+        l2 += l
+        assertEquals(list, l2)
+    }
+
+    @Test
+    fun addAllArrayAtIndex() {
+        val a1 = pKeyArrayOf(4KeySuffix)
+        val a2 = pKeyArrayOf(1KeySuffix, 2KeySuffix)
+        val a3 = pKeyArrayOf(5KeySuffix)
+        val l = mutablePKeyListOf(3KeySuffix)
+        assertTrue(l.addAll(1, a3))
+        assertTrue(l.addAll(0, a2))
+        assertTrue(l.addAll(3, a1))
+        assertFalse(l.addAll(0, pKeyArrayOf()))
+        assertEquals(list, l)
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l.addAll(6, pKeyArrayOf())
+        }
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l.addAll(-1, pKeyArrayOf())
+        }
+    }
+
+    @Test
+    fun addAllArray() {
+        val a = pKeyArrayOf(3KeySuffix, 4KeySuffix, 5KeySuffix)
+        val v = mutablePKeyListOf(1KeySuffix, 2KeySuffix)
+        v.addAll(a)
+        assertEquals(5, v.size)
+        assertEquals(3KeySuffix, v[2])
+        assertEquals(4KeySuffix, v[3])
+        assertEquals(5KeySuffix, v[4])
+    }
+
+    @Test
+    fun plusAssignArray() {
+        val a = pKeyArrayOf(3KeySuffix, 4KeySuffix, 5KeySuffix)
+        val v = mutablePKeyListOf(1KeySuffix, 2KeySuffix)
+        v += a
+        assertEquals(list, v)
+    }
+
+    @Test
+    fun clear() {
+        val l = mutablePKeyListOf()
+        l.addAll(list)
+        assertTrue(l.isNotEmpty())
+        l.clear()
+        assertTrue(l.isEmpty())
+    }
+
+    @Test
+    fun trim() {
+        val l = mutablePKeyListOf(1KeySuffix)
+        l.trim()
+        assertEquals(1, l.capacity)
+        l += pKeyArrayOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix)
+        l.trim()
+        assertEquals(6, l.capacity)
+        assertEquals(6, l.size)
+        l.clear()
+        l.trim()
+        assertEquals(0, l.capacity)
+        l.trim(100)
+        assertEquals(0, l.capacity)
+        l += pKeyArrayOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix)
+        l -= 5KeySuffix
+        l.trim(5)
+        assertEquals(5, l.capacity)
+        l.trim(4)
+        assertEquals(4, l.capacity)
+        l.trim(3)
+        assertEquals(4, l.capacity)
+    }
+
+    @Test
+    fun remove() {
+        val l = mutablePKeyListOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix)
+        l.remove(3KeySuffix)
+        assertEquals(mutablePKeyListOf(1KeySuffix, 2KeySuffix, 4KeySuffix, 5KeySuffix), l)
+    }
+
+    @Test
+    fun removeAt() {
+        val l = mutablePKeyListOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix)
+        l.removeAt(2)
+        assertEquals(mutablePKeyListOf(1KeySuffix, 2KeySuffix, 4KeySuffix, 5KeySuffix), l)
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l.removeAt(6)
+        }
+        assertFailsWith(IndexOutOfBoundsException::class) {
+            l.removeAt(-1)
+        }
+    }
+
+    @Test
+    fun set() {
+        val l = mutablePKeyListOf(0KeySuffix, 0KeySuffix, 0KeySuffix, 0KeySuffix, 0KeySuffix)
+        l[0] = 1KeySuffix
+        l[4] = 5KeySuffix
+        l[2] = 3KeySuffix
+        l[1] = 2KeySuffix
+        l[3] = 4KeySuffix
+        assertEquals(list, l)
+        assertFailsWith<IndexOutOfBoundsException> {
+            l.set(-1, 1KeySuffix)
+        }
+        assertFailsWith<IndexOutOfBoundsException> {
+            l.set(6, 1KeySuffix)
+        }
+        assertEquals(4KeySuffix, l.set(3, 1KeySuffix));
+    }
+
+    @Test
+    fun ensureCapacity() {
+        val l = mutablePKeyListOf(1KeySuffix)
+        assertEquals(1, l.capacity)
+        l.ensureCapacity(5)
+        assertEquals(5, l.capacity)
+    }
+
+    @Test
+    fun removeAllList() {
+        assertFalse(list.removeAll(mutablePKeyListOf(0KeySuffix, 10KeySuffix, 15KeySuffix)))
+        val l = mutablePKeyListOf(0KeySuffix, 1KeySuffix, 15KeySuffix, 10KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix, 20KeySuffix, 5KeySuffix)
+        assertTrue(l.removeAll(mutablePKeyListOf(20KeySuffix, 0KeySuffix, 15KeySuffix, 10KeySuffix, 5KeySuffix)))
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun removeAllPKeyArray() {
+        assertFalse(list.removeAll(pKeyArrayOf(0KeySuffix, 10KeySuffix, 15KeySuffix)))
+        val l = mutablePKeyListOf(0KeySuffix, 1KeySuffix, 15KeySuffix, 10KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix, 20KeySuffix, 5KeySuffix)
+        assertTrue(l.removeAll(pKeyArrayOf(20KeySuffix, 0KeySuffix, 15KeySuffix, 10KeySuffix, 5KeySuffix)))
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun minusAssignList() {
+        val l = mutablePKeyListOf().also { it += list }
+        l -= mutablePKeyListOf(0KeySuffix, 10KeySuffix, 15KeySuffix)
+        assertEquals(list, l)
+        val l2 = mutablePKeyListOf(0KeySuffix, 1KeySuffix, 15KeySuffix, 10KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix, 20KeySuffix, 5KeySuffix)
+        l2 -= mutablePKeyListOf(20KeySuffix, 0KeySuffix, 15KeySuffix, 10KeySuffix, 5KeySuffix)
+        assertEquals(list, l2)
+    }
+
+    @Test
+    fun minusAssignPKeyArray() {
+        val l = mutablePKeyListOf().also { it += list }
+        l -= pKeyArrayOf(0KeySuffix, 10KeySuffix, 15KeySuffix)
+        assertEquals(list, l)
+        val l2 = mutablePKeyListOf(0KeySuffix, 1KeySuffix, 15KeySuffix, 10KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix, 20KeySuffix, 5KeySuffix)
+        l2 -= pKeyArrayOf(20KeySuffix, 0KeySuffix, 15KeySuffix, 10KeySuffix, 5KeySuffix)
+        assertEquals(list, l2)
+    }
+
+    @Test
+    fun retainAll() {
+        assertFalse(list.retainAll(mutablePKeyListOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix, 6KeySuffix)))
+        val l = mutablePKeyListOf(0KeySuffix, 1KeySuffix, 15KeySuffix, 10KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix, 20KeySuffix)
+        assertTrue(l.retainAll(mutablePKeyListOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix, 6KeySuffix)))
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun retainAllPKeyArray() {
+        assertFalse(list.retainAll(pKeyArrayOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix, 6KeySuffix)))
+        val l = mutablePKeyListOf(0KeySuffix, 1KeySuffix, 15KeySuffix, 10KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix, 20KeySuffix)
+        assertTrue(l.retainAll(pKeyArrayOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix, 6KeySuffix)))
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun removeRange() {
+        val l = mutablePKeyListOf(1KeySuffix, 9KeySuffix, 7KeySuffix, 6KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix)
+        l.removeRange(1, 4)
+        assertEquals(list, l)
+        assertFailsWith<IndexOutOfBoundsException> {
+            l.removeRange(6, 6)
+        }
+        assertFailsWith<IndexOutOfBoundsException> {
+            l.removeRange(100, 200)
+        }
+        assertFailsWith<IndexOutOfBoundsException> {
+            l.removeRange(-1, 0)
+        }
+        assertFailsWith<IllegalArgumentException> {
+            l.removeRange(3, 2)
+        }
+    }
+
+    @Test
+    fun sort() {
+        val l = mutablePKeyListOf(1KeySuffix, 4KeySuffix, 2KeySuffix, 5KeySuffix, 3KeySuffix)
+        l.sort()
+        assertEquals(list, l)
+    }
+
+    @Test
+    fun sortDescending() {
+        val l = mutablePKeyListOf(1KeySuffix, 4KeySuffix, 2KeySuffix, 5KeySuffix, 3KeySuffix)
+        l.sortDescending()
+        assertEquals(mutablePKeyListOf(5KeySuffix, 4KeySuffix, 3KeySuffix, 2KeySuffix, 1KeySuffix), l)
+    }
+
+    @Test
+    fun testEmptyPKeyList() {
+        val l = emptyPKeyList()
+        assertEquals(0, l.size)
+    }
+
+    @Test
+    fun pKeyListOfEmpty() {
+        val l = pKeyListOf()
+        assertEquals(0, l.size)
+    }
+
+    @Test
+    fun pKeyListOfOneValue() {
+        val l = pKeyListOf(2KeySuffix)
+        assertEquals(1, l.size)
+        assertEquals(2KeySuffix, l[0])
+    }
+
+    @Test
+    fun pKeyListOfTwoValues() {
+        val l = pKeyListOf(2KeySuffix, 1KeySuffix)
+        assertEquals(2, l.size)
+        assertEquals(2KeySuffix, l[0])
+        assertEquals(1KeySuffix, l[1])
+    }
+
+    @Test
+    fun pKeyListOfThreeValues() {
+        val l = pKeyListOf(2KeySuffix, 10KeySuffix, -1KeySuffix)
+        assertEquals(3, l.size)
+        assertEquals(2KeySuffix, l[0])
+        assertEquals(10KeySuffix, l[1])
+        assertEquals(-1KeySuffix, l[2])
+    }
+
+    @Test
+    fun pKeyListOfFourValues() {
+        val l = pKeyListOf(2KeySuffix, 10KeySuffix, -1KeySuffix, 10KeySuffix)
+        assertEquals(4, l.size)
+        assertEquals(2KeySuffix, l[0])
+        assertEquals(10KeySuffix, l[1])
+        assertEquals(-1KeySuffix, l[2])
+        assertEquals(10KeySuffix, l[3])
+    }
+
+    @Test
+    fun mutablePKeyListOfOneValue() {
+        val l = mutablePKeyListOf(2KeySuffix)
+        assertEquals(1, l.size)
+        assertEquals(1, l.capacity)
+        assertEquals(2KeySuffix, l[0])
+    }
+
+    @Test
+    fun mutablePKeyListOfTwoValues() {
+        val l = mutablePKeyListOf(2KeySuffix, 1KeySuffix)
+        assertEquals(2, l.size)
+        assertEquals(2, l.capacity)
+        assertEquals(2KeySuffix, l[0])
+        assertEquals(1KeySuffix, l[1])
+    }
+
+    @Test
+    fun mutablePKeyListOfThreeValues() {
+        val l = mutablePKeyListOf(2KeySuffix, 10KeySuffix, -1KeySuffix)
+        assertEquals(3, l.size)
+        assertEquals(3, l.capacity)
+        assertEquals(2KeySuffix, l[0])
+        assertEquals(10KeySuffix, l[1])
+        assertEquals(-1KeySuffix, l[2])
+    }
+
+    @Test
+    fun mutablePKeyListOfFourValues() {
+        val l = mutablePKeyListOf(2KeySuffix, 10KeySuffix, -1KeySuffix, 10KeySuffix)
+        assertEquals(4, l.size)
+        assertEquals(4, l.capacity)
+        assertEquals(2KeySuffix, l[0])
+        assertEquals(10KeySuffix, l[1])
+        assertEquals(-1KeySuffix, l[2])
+        assertEquals(10KeySuffix, l[3])
+    }
+}
diff --git a/collection/collection/template/PKeyObjectMap.kt.template b/collection/collection/template/PKeyObjectMap.kt.template
new file mode 100644
index 0000000..2bb53a2b
--- /dev/null
+++ b/collection/collection/template/PKeyObjectMap.kt.template
@@ -0,0 +1,847 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import androidx.collection.internal.EMPTY_OBJECTS
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyPKeyObjectMap = MutablePKeyObjectMap<Nothing>(0)
+
+/**
+ * Returns an empty, read-only [PKeyObjectMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <V> emptyPKeyObjectMap(): PKeyObjectMap<V> = EmptyPKeyObjectMap as PKeyObjectMap<V>
+
+/**
+ * Returns an empty, read-only [PKeyObjectMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun <V> pKeyObjectMapOf(): PKeyObjectMap<V> = EmptyPKeyObjectMap as PKeyObjectMap<V>
+
+/**
+ * Returns a new [PKeyObjectMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * the [PKey] key is boxed. Use [set] for each entry instead when it is
+ * important to reduce allocations.
+ */
+public fun <V> pKeyObjectMapOf(vararg pairs: Pair<PKey, V>): PKeyObjectMap<V> =
+    MutablePKeyObjectMap<V>(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutablePKeyObjectMap].
+ */
+public fun <V> mutablePKeyObjectMapOf(): MutablePKeyObjectMap<V> = MutablePKeyObjectMap()
+
+/**
+ * Returns a new [MutablePKeyObjectMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * the [PKey] key is boxed. Use [set] for each entry instead when it is
+ * important to reduce allocations.
+ */
+public fun <V> mutablePKeyObjectMapOf(vararg pairs: Pair<PKey, V>): MutablePKeyObjectMap<V> =
+    MutablePKeyObjectMap<V>(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [PKeyObjectMap] is a container with a [Map]-like interface for keys with
+ * [PKey] primitives and reference type values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutablePKeyObjectMap].
+ *
+ * @see [MutablePKeyObjectMap]
+ */
+public sealed class PKeyObjectMap<V> {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: PKeyArray = EmptyPKeyArray
+
+    @PublishedApi
+    @JvmField
+    internal var values: Array<Any?> = EMPTY_OBJECTS
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key], or `null` if such
+     * a key is not present in the map.
+     */
+    public operator fun get(key: PKey): V? {
+        val index = findKeyIndex(key)
+        @Suppress("UNCHECKED_CAST")
+        return if (index >= 0) values[index] as V? else null
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: PKey, defaultValue: V): V {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            @Suppress("UNCHECKED_CAST")
+            return values[index] as V
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: PKey, defaultValue: () -> V): V {
+        return get(key) ?: defaultValue()
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: PKey, value: V) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            block(k[index], v[index] as V)
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: PKey) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            block(k[index])
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: V) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            block(v[index] as V)
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (PKey, V) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (PKey, V) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (PKey, V) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: PKey): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: PKey): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: V): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [PKeyObjectMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is PKeyObjectMap<*>) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        forEach { key, value ->
+            if (value == null) {
+                if (other[key] != null || !other.containsKey(key)) {
+                    return false
+                }
+            } else if (value != other[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(key)
+            s.append("=")
+            s.append(if (value === this) "(this)" else value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    internal inline fun findKeyIndex(key: PKey): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutablePKeyObjectMap] is a container with a [MutableMap]-like interface for keys with
+ * [PKey] primitives and reference type values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutablePKeyObjectMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see ScatterMap
+ */
+public class MutablePKeyObjectMap<V>(
+    initialCapacity: Int = DefaultScatterCapacity
+) : PKeyObjectMap<V>() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = PKeyArray(newCapacity)
+        values = arrayOfNulls(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: PKey, defaultValue: () -> V): V {
+        return get(key) ?: defaultValue().also { set(key, it) }
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: PKey, value: V) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations. Return the previous value associated with the [key],
+     * or `null` if the key was not present in the map.
+     */
+    public fun put(key: PKey, value: V): V? {
+        val index = findAbsoluteInsertIndex(key)
+        val oldValue = values[index]
+        keys[index] = key
+        values[index] = value
+
+        @Suppress("UNCHECKED_CAST")
+        return oldValue as V?
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * the [PKey] key is boxed. Use [set] for each entry instead when it is
+     * important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<out Pair<PKey, V>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: PKeyObjectMap<V>) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that the [Pair] is allocated and the [PKey] key is boxed.
+     * Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<PKey, V>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * the [PKey] key is boxed. Use [set] for each entry instead when it is
+     * important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<out Pair<PKey, V>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: PKeyObjectMap<V>): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map. If the
+     * [key] was present in the map, this function returns the value that was
+     * present before removal.
+     */
+    public fun remove(key: PKey): V? {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return removeValueAt(index)
+        }
+        return null
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: PKey, value: V): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (PKey, V) -> Boolean) {
+        forEachIndexed { index ->
+            @Suppress("UNCHECKED_CAST")
+            if (predicate(keys[index], values[index] as V)) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: PKey) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: PKeyArray) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: PKeySet) {
+        keys.forEach { key ->
+            minusAssign(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: PKeyList) {
+        keys.forEach { key ->
+            minusAssign(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int): V? {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+        val oldValue = values[index]
+        values[index] = null
+
+        @Suppress("UNCHECKED_CAST")
+        return oldValue as V?
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        values.fill(null, 0, _capacity)
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: PKey): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutablePKeyObjectMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/template/PKeyObjectMapTest.kt.template b/collection/collection/template/PKeyObjectMapTest.kt.template
new file mode 100644
index 0000000..d04a330
--- /dev/null
+++ b/collection/collection/template/PKeyObjectMapTest.kt.template
@@ -0,0 +1,632 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+class PKeyObjectMapTest {
+    @Test
+    fun pKeyObjectMap() {
+        val map = MutablePKeyObjectMap<String>()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun testEmptyPKeyObjectMap() {
+        val map = emptyPKeyObjectMap<String>()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyPKeyObjectMap<String>(), map)
+    }
+
+    @Test
+    fun pKeyObjectMapFunction() {
+        val map = mutablePKeyObjectMapOf<String>()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutablePKeyObjectMap<String>(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun pKeyObjectMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutablePKeyObjectMap<String>(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun pKeyObjectMapPairsFunction() {
+        val map = mutablePKeyObjectMapOf(
+            1KeySuffix to "World",
+            2KeySuffix to "Monde"
+        )
+        assertEquals(2, map.size)
+        assertEquals("World", map[1KeySuffix])
+        assertEquals("Monde", map[2KeySuffix])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutablePKeyObjectMap<String>()
+        map[1KeySuffix] = "World"
+
+        assertEquals(1, map.size)
+        assertEquals("World", map[1KeySuffix])
+    }
+
+    @Test
+    fun insertIndex0() {
+        val map = MutablePKeyObjectMap<String>()
+        map.put(1KeySuffix, "World")
+        assertEquals("World", map[1KeySuffix])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutablePKeyObjectMap<String>(12)
+        map[1KeySuffix] = "World"
+
+        assertEquals(1, map.size)
+        assertEquals("World", map[1KeySuffix])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutablePKeyObjectMap<String>(2)
+        map[1KeySuffix] = "World"
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals("World", map[1KeySuffix])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutablePKeyObjectMap<String>(0)
+        map[1KeySuffix] = "World"
+
+        assertEquals(1, map.size)
+        assertEquals("World", map[1KeySuffix])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutablePKeyObjectMap<String>()
+        map[1KeySuffix] = "World"
+        map[1KeySuffix] = "Monde"
+
+        assertEquals(1, map.size)
+        assertEquals("Monde", map[1KeySuffix])
+    }
+
+    @Test
+    fun put() {
+        val map = MutablePKeyObjectMap<String?>()
+
+        assertNull(map.put(1KeySuffix, "World"))
+        assertEquals("World", map.put(1KeySuffix, "Monde"))
+        assertNull(map.put(2KeySuffix, null))
+        assertNull(map.put(2KeySuffix, "Monde"))
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutablePKeyObjectMap<String?>()
+        map[1KeySuffix] = "World"
+        map[2KeySuffix] = null
+
+        map.putAll(arrayOf(3KeySuffix to "Welt", 7KeySuffix to "Mundo"))
+
+        assertEquals(4, map.size)
+        assertEquals("Welt", map[3KeySuffix])
+        assertEquals("Mundo", map[7KeySuffix])
+    }
+
+    @Test
+    fun putAllMap() {
+        val map = MutablePKeyObjectMap<String?>()
+        map[1KeySuffix] = "World"
+        map[2KeySuffix] = null
+
+        map.putAll(mutablePKeyObjectMapOf(3KeySuffix to "Welt", 7KeySuffix to "Mundo"))
+
+        assertEquals(4, map.size)
+        assertEquals("Welt", map[3KeySuffix])
+        assertEquals("Mundo", map[7KeySuffix])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutablePKeyObjectMap<String>()
+        map += 1KeySuffix to "World"
+
+        assertEquals(1, map.size)
+        assertEquals("World", map[1KeySuffix])
+    }
+
+    @Test
+    fun plusMap() {
+        val map = MutablePKeyObjectMap<String>()
+        map += pKeyObjectMapOf(3KeySuffix to "Welt", 7KeySuffix to "Mundo")
+
+        assertEquals(2, map.size)
+        assertEquals("Welt", map[3KeySuffix])
+        assertEquals("Mundo", map[7KeySuffix])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutablePKeyObjectMap<String>()
+        map += arrayOf(3KeySuffix to "Welt", 7KeySuffix to "Mundo")
+
+        assertEquals(2, map.size)
+        assertEquals("Welt", map[3KeySuffix])
+        assertEquals("Mundo", map[7KeySuffix])
+    }
+
+    @Test
+    fun nullValue() {
+        val map = MutablePKeyObjectMap<String?>()
+        map[1KeySuffix] = null
+
+        assertEquals(1, map.size)
+        assertNull(map[1KeySuffix])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutablePKeyObjectMap<String?>()
+        map[1KeySuffix] = "World"
+
+        assertNull(map[2KeySuffix])
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutablePKeyObjectMap<String?>()
+        map[1KeySuffix] = "World"
+
+        assertEquals("Monde", map.getOrDefault(2KeySuffix, "Monde"))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutablePKeyObjectMap<String?>()
+        map[1KeySuffix] = "World"
+        map[2KeySuffix] = null
+
+        assertEquals("Monde", map.getOrElse(2KeySuffix) { "Monde" })
+        assertEquals("Welt", map.getOrElse(3KeySuffix) { "Welt" })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutablePKeyObjectMap<String?>()
+        map[1KeySuffix] = "World"
+
+        var counter = 0
+        map.getOrPut(1KeySuffix) {
+            counter++
+            "Monde"
+        }
+        assertEquals("World", map[1KeySuffix])
+        assertEquals(0, counter)
+
+        map.getOrPut(2KeySuffix) {
+            counter++
+            "Monde"
+        }
+        assertEquals("Monde", map[2KeySuffix])
+        assertEquals(1, counter)
+
+        map.getOrPut(2KeySuffix) {
+            counter++
+            "Welt"
+        }
+        assertEquals("Monde", map[2KeySuffix])
+        assertEquals(1, counter)
+
+        map.getOrPut(3KeySuffix) {
+            counter++
+            null
+        }
+        assertNull(map[3KeySuffix])
+        assertEquals(2, counter)
+
+        map.getOrPut(3KeySuffix) {
+            counter++
+            "Welt"
+        }
+        assertEquals("Welt", map[3KeySuffix])
+        assertEquals(3, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutablePKeyObjectMap<String?>()
+        assertNull(map.remove(1KeySuffix))
+
+        map[1KeySuffix] = "World"
+        assertEquals("World", map.remove(1KeySuffix))
+        assertEquals(0, map.size)
+
+        map[1KeySuffix] = null
+        assertNull(map.remove(1KeySuffix))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutablePKeyObjectMap<String>(6)
+        map[1KeySuffix] = "World"
+        map[2KeySuffix] = "Monde"
+        map[3KeySuffix] = "Welt"
+        map[4KeySuffix] = "Sekai"
+        map[5KeySuffix] = "Mondo"
+        map[6KeySuffix] = "Sesang"
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove(1KeySuffix)
+        map.remove(2KeySuffix)
+        map.remove(3KeySuffix)
+        map.remove(4KeySuffix)
+        map.remove(5KeySuffix)
+        map.remove(6KeySuffix)
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map[7KeySuffix] = "Mundo"
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutablePKeyObjectMap<String>()
+        map[1KeySuffix] = "World"
+        map[2KeySuffix] = "Monde"
+        map[3KeySuffix] = "Welt"
+        map[4KeySuffix] = "Sekai"
+        map[5KeySuffix] = "Mondo"
+        map[6KeySuffix] = "Sesang"
+
+        map.removeIf { key, value ->
+            key == 1KeySuffix || key == 3KeySuffix || value.startsWith('S')
+        }
+
+        assertEquals(2, map.size)
+        assertEquals("Monde", map[2KeySuffix])
+        assertEquals("Mondo", map[5KeySuffix])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutablePKeyObjectMap<String>()
+        map[1KeySuffix] = "World"
+        map[2KeySuffix] = "Monde"
+        map[3KeySuffix] = "Welt"
+
+        map -= 1KeySuffix
+
+        assertEquals(2, map.size)
+        assertNull(map[1KeySuffix])
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutablePKeyObjectMap<String>()
+        map[1KeySuffix] = "World"
+        map[2KeySuffix] = "Monde"
+        map[3KeySuffix] = "Welt"
+
+        map -= pKeyArrayOf(3KeySuffix, 2KeySuffix)
+
+        assertEquals(1, map.size)
+        assertNull(map[3KeySuffix])
+        assertNull(map[2KeySuffix])
+    }
+
+    @Test
+    fun minusSet() {
+        val map = MutablePKeyObjectMap<String>()
+        map[1KeySuffix] = "World"
+        map[2KeySuffix] = "Monde"
+        map[3KeySuffix] = "Welt"
+
+        map -= pKeySetOf(3KeySuffix, 2KeySuffix)
+
+        assertEquals(1, map.size)
+        assertNull(map[3KeySuffix])
+        assertNull(map[2KeySuffix])
+    }
+
+    @Test
+    fun minusList() {
+        val map = MutablePKeyObjectMap<String>()
+        map[1KeySuffix] = "World"
+        map[2KeySuffix] = "Monde"
+        map[3KeySuffix] = "Welt"
+
+        map -= pKeyListOf(3KeySuffix, 2KeySuffix)
+
+        assertEquals(1, map.size)
+        assertNull(map[3KeySuffix])
+        assertNull(map[2KeySuffix])
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutablePKeyObjectMap<String?>()
+        assertFalse(map.remove(1KeySuffix, "World"))
+
+        map[1KeySuffix] = "World"
+        assertTrue(map.remove(1KeySuffix, "World"))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutablePKeyObjectMap<String>()
+
+        for (i in 0 until 1700) {
+            map[i.toPKey()] = i.toString()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutablePKeyObjectMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toPKey()] = j.toString()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key.toInt().toString(), value)
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutablePKeyObjectMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toPKey()] = j.toString()
+            }
+
+            var counter = 0
+            map.forEachKey { key ->
+                assertEquals(key.toInt().toString(), map[key])
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutablePKeyObjectMap<String>()
+
+            for (j in 0 until i) {
+                map[j.toPKey()] = j.toString()
+            }
+
+            var counter = 0
+            map.forEachValue { value ->
+                assertNotNull(value.toIntOrNull())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutablePKeyObjectMap<String>()
+
+        for (i in 0 until 32) {
+            map[i.toPKey()] = i.toString()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutablePKeyObjectMap<String?>()
+        assertEquals("{}", map.toString())
+
+        map[1KeySuffix] = "World"
+        map[2KeySuffix] = "Monde"
+        val oneKey = 1KeySuffix.toString()
+        val twoKey = 2KeySuffix.toString()
+        assertTrue(
+            "{$oneKey=World, $twoKey=Monde}" == map.toString() ||
+                "{$twoKey=Monde, $oneKey=World}" == map.toString()
+        )
+
+        map.clear()
+        map[1KeySuffix] = null
+        assertEquals("{$oneKey=null}", map.toString())
+
+        val selfAsValueMap = MutablePKeyObjectMap<Any>()
+        selfAsValueMap[1KeySuffix] = selfAsValueMap
+        assertEquals("{$oneKey=(this)}", selfAsValueMap.toString())
+    }
+
+    @Test
+    fun equals() {
+        val map = MutablePKeyObjectMap<String?>()
+        map[1KeySuffix] = "World"
+        map[2KeySuffix] = null
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutablePKeyObjectMap<String?>()
+        map2[2KeySuffix] = null
+
+        assertNotEquals(map, map2)
+
+        map2[1KeySuffix] = "World"
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutablePKeyObjectMap<String?>()
+        map[1KeySuffix] = "World"
+        map[2KeySuffix] = null
+
+        assertTrue(map.containsKey(1KeySuffix))
+        assertFalse(map.containsKey(3KeySuffix))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutablePKeyObjectMap<String?>()
+        map[1KeySuffix] = "World"
+        map[2KeySuffix] = null
+
+        assertTrue(1KeySuffix in map)
+        assertFalse(3KeySuffix in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutablePKeyObjectMap<String?>()
+        map[1KeySuffix] = "World"
+        map[2KeySuffix] = null
+
+        assertTrue(map.containsValue("World"))
+        assertTrue(map.containsValue(null))
+        assertFalse(map.containsValue("Monde"))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutablePKeyObjectMap<String?>()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map[1KeySuffix] = "World"
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutablePKeyObjectMap<String>()
+        assertEquals(0, map.count())
+
+        map[1KeySuffix] = "World"
+        assertEquals(1, map.count())
+
+        map[2KeySuffix] = "Monde"
+        map[3KeySuffix] = "Welt"
+        map[4KeySuffix] = "Sekai"
+        map[5KeySuffix] = "Mondo"
+        map[6KeySuffix] = "Sesang"
+
+        assertEquals(2, map.count { key, _ -> key < 3KeySuffix })
+        assertEquals(0, map.count { key, _ -> key < 0KeySuffix })
+    }
+
+    @Test
+    fun any() {
+        val map = MutablePKeyObjectMap<String>()
+        map[1KeySuffix] = "World"
+        map[2KeySuffix] = "Monde"
+        map[3KeySuffix] = "Welt"
+        map[4KeySuffix] = "Sekai"
+        map[5KeySuffix] = "Mondo"
+        map[6KeySuffix] = "Sesang"
+
+        assertTrue(map.any { key, _ -> key > 5KeySuffix })
+        assertFalse(map.any { key, _ -> key < 0KeySuffix })
+    }
+
+    @Test
+    fun all() {
+        val map = MutablePKeyObjectMap<String>()
+        map[1KeySuffix] = "World"
+        map[2KeySuffix] = "Monde"
+        map[3KeySuffix] = "Welt"
+        map[4KeySuffix] = "Sekai"
+        map[5KeySuffix] = "Mondo"
+        map[6KeySuffix] = "Sesang"
+
+        assertTrue(map.all { key, value -> key < 7KeySuffix && value.length > 0 })
+        assertFalse(map.all { key, _ -> key < 6KeySuffix })
+    }
+}
diff --git a/collection/collection/template/PKeyPValueMap.kt.template b/collection/collection/template/PKeyPValueMap.kt.template
new file mode 100644
index 0000000..0d0a6fe
--- /dev/null
+++ b/collection/collection/template/PKeyPValueMap.kt.template
@@ -0,0 +1,837 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// Default empty map to avoid allocations
+private val EmptyPKeyPValueMap = MutablePKeyPValueMap(0)
+
+/**
+ * Returns an empty, read-only [PKeyPValueMap].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun emptyPKeyPValueMap(): PKeyPValueMap = EmptyPKeyPValueMap
+
+/**
+ * Returns a new [MutablePKeyPValueMap].
+ */
+public fun pKeyPValueMapOf(): PKeyPValueMap = EmptyPKeyPValueMap
+
+/**
+ * Returns a new [PKeyPValueMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [PKey] key and [PValue] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun pKeyPValueMapOf(vararg pairs: Pair<PKey, PValue>): PKeyPValueMap =
+    MutablePKeyPValueMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * Returns a new [MutablePKeyPValueMap].
+ */
+public fun mutablePKeyPValueMapOf(): MutablePKeyPValueMap = MutablePKeyPValueMap()
+
+/**
+ * Returns a new [MutablePKeyPValueMap] with the specified contents, given as
+ * a list of pairs where the first component is the key and the second
+ * is the value. If multiple pairs have the same key, the resulting map
+ * will contain the value from the last of those pairs.
+ *
+ * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+ * both the [PKey] key and [PValue] value are boxed. Use [set] for each
+ * entry instead when it is important to reduce allocations.
+ */
+public fun mutablePKeyPValueMapOf(vararg pairs: Pair<PKey, PValue>): MutablePKeyPValueMap =
+    MutablePKeyPValueMap(pairs.size).also { map ->
+        pairs.forEach { (key, value) -> map[key] = value }
+    }
+
+/**
+ * [PKeyPValueMap] is a container with a [Map]-like interface for
+ * [PKey] primitive keys and [PValue] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * This implementation is read-only and only allows data to be queried. A
+ * mutable implementation is provided by [MutablePKeyPValueMap].
+ *
+ * @see [MutablePKeyPValueMap]
+ * @see [ScatterMap]
+ */
+public sealed class PKeyPValueMap {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` entries, including when
+    // the table is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var keys: PKeyArray = EmptyPKeyArray
+
+    @PublishedApi
+    @JvmField
+    internal var values: PValueArray = EmptyPValueArray
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of key-value pairs that can be stored in this map
+     * without requiring internal storage reallocation.
+     */
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @Suppress("PropertyName")
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of key-value pairs in this map.
+     */
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this map has at least one entry.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this map has no entries.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this map is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this map is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the value corresponding to the given [key].
+     * @throws NoSuchElementException if [key] is not in the map
+     */
+    public operator fun get(key: PKey): PValue {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            throw NoSuchElementException("Cannot find value for key $key")
+        }
+        return values[index]
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * or [defaultValue] if this map contains no mapping for the key.
+     */
+    public fun getOrDefault(key: PKey, defaultValue: PValue): PValue {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            return values[index]
+        }
+        return defaultValue
+    }
+
+    /**
+     * Returns the value for the given [key] if the value is present
+     * and not null. Otherwise, returns the result of the [defaultValue]
+     * function.
+     */
+    public inline fun getOrElse(key: PKey, defaultValue: () -> PValue): PValue {
+        val index = findKeyIndex(key)
+        if (index < 0) {
+            return defaultValue()
+        }
+        return values[index]
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndexed(block: (index: Int) -> Unit) {
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 entries
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every key/value pair stored in this map by invoking
+     * the specified [block] lambda.
+     */
+    public inline fun forEach(block: (key: PKey, value: PValue) -> Unit) {
+        val k = keys
+        val v = values
+
+        forEachIndexed { index ->
+            block(k[index], v[index])
+        }
+    }
+
+    /**
+     * Iterates over every key stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachKey(block: (key: PKey) -> Unit) {
+        val k = keys
+
+        forEachIndexed { index ->
+            block(k[index])
+        }
+    }
+
+    /**
+     * Iterates over every value stored in this map by invoking the specified
+     * [block] lambda.
+     */
+    public inline fun forEachValue(block: (value: PValue) -> Unit) {
+        val v = values
+
+        forEachIndexed { index ->
+            block(v[index])
+        }
+    }
+
+    /**
+     * Returns true if all entries match the given [predicate].
+     */
+    public inline fun all(predicate: (PKey, PValue) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (!predicate(key, value)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one entry matches the given [predicate].
+     */
+    public inline fun any(predicate: (PKey, PValue) -> Boolean): Boolean {
+        forEach { key, value ->
+            if (predicate(key, value)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of entries in this map.
+     */
+    public fun count(): Int = size
+
+    /**
+     * Returns the number of entries matching the given [predicate].
+     */
+    public inline fun count(predicate: (PKey, PValue) -> Boolean): Int {
+        var count = 0
+        forEach { key, value ->
+            if (predicate(key, value)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public operator fun contains(key: PKey): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [key] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsKey(key: PKey): Boolean = findKeyIndex(key) >= 0
+
+    /**
+     * Returns true if the specified [value] is present in this hash map, false
+     * otherwise.
+     */
+    public fun containsValue(value: PValue): Boolean {
+        forEachValue { v ->
+            if (value == v) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the hash code value for this map. The hash code the sum of the hash
+     * codes of each key/value pair.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { key, value ->
+            hash += key.hashCode() xor value.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this hash map for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [PKeyPValueMap]
+     * - Has the same [size] as this map
+     * - Contains key/value pairs equal to this map's pair
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is PKeyPValueMap) {
+            return false
+        }
+        if (other.size != size) {
+            return false
+        }
+
+        forEach { key, value ->
+            if (value != other[key]) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this map. The map is denoted in the
+     * string by the `{}`. Each key/value pair present in the map is represented
+     * inside '{}` by a substring of the form `key=value`, and pairs are
+     * separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "{}"
+        }
+
+        val s = StringBuilder().append('{')
+        var i = 0
+        forEach { key, value ->
+            s.append(key)
+            s.append("=")
+            s.append(value)
+            i++
+            if (i < _size) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append('}').toString()
+    }
+
+    /**
+     * Scans the hash table to find the index in the backing arrays of the
+     * specified [key]. Returns -1 if the key is not present.
+     */
+    @PublishedApi
+    internal fun findKeyIndex(key: PKey): Int {
+        val hash = hash(key)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutablePKeyPValueMap] is a container with a [MutableMap]-like interface for
+ * [PKey] primitive keys and [PValue] primitive values.
+ *
+ * The underlying implementation is designed to avoid allocations from boxing,
+ * and insertion, removal, retrieval, and iteration operations. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added entries to the table. In addition, this implementation
+ * minimizes memory usage by avoiding the use of separate objects to hold
+ * key/value pairs.
+ *
+ * This implementation makes no guarantee as to the order of the keys and
+ * values stored, nor does it make guarantees that the order remains constant
+ * over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the map (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Multiple threads are safe to read from this
+ * map concurrently if no write is happening.
+ *
+ * @constructor Creates a new [MutablePKeyPValueMap]
+ * @param initialCapacity The initial desired capacity for this container.
+ * the container will honor this value by guaranteeing its internal structures
+ * can hold that many entries without requiring any allocations. The initial
+ * capacity can be set to 0.
+ *
+ * @see MutableScatterMap
+ */
+public class MutablePKeyPValueMap(
+    initialCapacity: Int = DefaultScatterCapacity
+) : PKeyPValueMap() {
+    // Number of entries we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        keys = PKeyArray(newCapacity)
+        values = PValueArray(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Returns the value to which the specified [key] is mapped,
+     * if the value is present in the map and not `null`. Otherwise,
+     * calls `defaultValue()` and puts the result in the map associated
+     * with [key].
+     */
+    public inline fun getOrPut(key: PKey, defaultValue: () -> PValue): PValue {
+        val index = findKeyIndex(key)
+        return if (index < 0) {
+            val defValue = defaultValue()
+            put(key, defValue)
+            defValue
+        } else {
+            values[index]
+        }
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations.
+     */
+    public operator fun set(key: PKey, value: PValue) {
+        val index = findAbsoluteInsertIndex(key)
+        keys[index] = key
+        values[index] = value
+    }
+
+    /**
+     * Creates a new mapping from [key] to [value] in this map. If [key] is
+     * already present in the map, the association is modified and the previously
+     * associated value is replaced with [value]. If [key] is not present, a new
+     * entry is added to the map, which may require to grow the underlying storage
+     * and cause allocations. Return the previous value associated with the [key],
+     * or `null` if the key was not present in the map.
+     */
+    public fun put(key: PKey, value: PValue) {
+        set(key, value)
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [PKey] key and [PValue] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public fun putAll(@Suppress("ArrayReturn") pairs: Array<Pair<PKey, PValue>>) {
+        for ((key, value) in pairs) {
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public fun putAll(from: PKeyPValueMap) {
+        from.forEach { key, value ->
+            this[key] = value
+        }
+    }
+
+    /**
+     * Puts the key/value mapping from the [pair] in this map, using the first
+     * element as the key, and the second element as the value.
+     *
+     * Note that [pair] allocated and both the [PKey] key and [PValue] value are
+     * boxed. Use [set] instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(pair: Pair<PKey, PValue>) {
+        this[pair.first] = pair.second
+    }
+
+    /**
+     * Puts all the [pairs] into this map, using the first component of the pair
+     * as the key, and the second component as the value.
+     *
+     * Note that [pairs] is an allocated array, and each [Pair] is allocated and
+     * both the [PKey] key and [PValue] value are boxed. Use [set] for each
+     * entry instead when it is important to reduce allocations.
+     */
+    public inline operator fun plusAssign(
+        @Suppress("ArrayReturn") pairs: Array<Pair<PKey, PValue>>
+    ): Unit = putAll(pairs)
+
+    /**
+     * Puts all the key/value mappings in the [from] map into this map.
+     */
+    public inline operator fun plusAssign(from: PKeyPValueMap): Unit = putAll(from)
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public fun remove(key: PKey) {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            removeValueAt(index)
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map if the
+     * associated value equals [value]. Returns whether the removal happened.
+     */
+    public fun remove(key: PKey, value: PValue): Boolean {
+        val index = findKeyIndex(key)
+        if (index >= 0) {
+            if (values[index] == value) {
+                removeValueAt(index)
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Removes any mapping for which the specified [predicate] returns true.
+     */
+    public fun removeIf(predicate: (PKey, PValue) -> Boolean) {
+        forEachIndexed { index ->
+            if (predicate(keys[index], values[index])) {
+                removeValueAt(index)
+            }
+        }
+    }
+
+    /**
+     * Removes the specified [key] and its associated value from the map.
+     */
+    public inline operator fun minusAssign(key: PKey) {
+        remove(key)
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(@Suppress("ArrayReturn") keys: PKeyArray) {
+        for (key in keys) {
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: PKeySet) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: PKeyList) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    private fun removeValueAt(index: Int) {
+        _size -= 1
+
+        // TODO: We could just mark the entry as empty if there's a group
+        //       window around this entry that was already empty
+        writeMetadata(index, Deleted)
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the hash table to find the index at which we can store a value
+     * for the give [key]. If the key already exists in the table, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the table is full.
+     */
+    private fun findAbsoluteInsertIndex(key: PKey): Int {
+        val hash = hash(key)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (keys[index] == key) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the table in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutablePKeyPValueMap]'s storage so it is sized appropriately
+     * to hold the current mappings.
+     *
+     * Returns the number of empty entries removed from this map's storage.
+     * Returns be 0 if no trimming is necessary or possible.
+     */
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted entries from the table to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the table capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_set`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousKeys = keys
+        val previousValues = values
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newKeys = keys
+        val newValues = values
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousKey = previousKeys[i]
+                val hash = hash(previousKey)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newKeys[index] = previousKey
+                newValues[index] = previousValues[i]
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
diff --git a/collection/collection/template/PKeyPValueMapTest.kt.template b/collection/collection/template/PKeyPValueMapTest.kt.template
new file mode 100644
index 0000000..c8f3cd5
--- /dev/null
+++ b/collection/collection/template/PKeyPValueMapTest.kt.template
@@ -0,0 +1,602 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+//
+// Note that there are 3 templates for maps, one for object-to-primitive, one
+// for primitive-to-object and one for primitive-to-primitive. Also, the
+// object-to-object is ScatterMap.kt, which doesn't have a template.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+@Suppress("RemoveRedundantCallsOfConversionMethods")
+class PKeyPValueMapTest {
+    @Test
+    fun pKeyPValueMap() {
+        val map = MutablePKeyPValueMap()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun testEmptyPKeyPValueMap() {
+        val map = emptyPKeyPValueMap()
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+
+        assertSame(emptyPKeyPValueMap(), map)
+    }
+
+    @Test
+    fun pKeyPValueMapFunction() {
+        val map = mutablePKeyPValueMapOf()
+        assertEquals(7, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun zeroCapacityHashMap() {
+        val map = MutablePKeyPValueMap(0)
+        assertEquals(0, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun pKeyPValueMapWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val map = MutablePKeyPValueMap(1800)
+        assertEquals(4095, map.capacity)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun pKeyPValueMapPairsFunction() {
+        val map = mutablePKeyPValueMapOf(
+            1KeySuffix to 1ValueSuffix,
+            2KeySuffix to 2ValueSuffix
+        )
+        assertEquals(2, map.size)
+        assertEquals(1ValueSuffix, map[1KeySuffix])
+        assertEquals(2ValueSuffix, map[2KeySuffix])
+    }
+
+    @Test
+    fun addToMap() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+
+        assertEquals(1, map.size)
+        assertEquals(1ValueSuffix, map[1KeySuffix])
+    }
+
+    @Test
+    fun addToSizedMap() {
+        val map = MutablePKeyPValueMap(12)
+        map[1KeySuffix] = 1ValueSuffix
+
+        assertEquals(1, map.size)
+        assertEquals(1ValueSuffix, map[1KeySuffix])
+    }
+
+    @Test
+    fun addToSmallMap() {
+        val map = MutablePKeyPValueMap(2)
+        map[1KeySuffix] = 1ValueSuffix
+
+        assertEquals(1, map.size)
+        assertEquals(7, map.capacity)
+        assertEquals(1ValueSuffix, map[1KeySuffix])
+    }
+
+    @Test
+    fun addToZeroCapacityMap() {
+        val map = MutablePKeyPValueMap(0)
+        map[1KeySuffix] = 1ValueSuffix
+
+        assertEquals(1, map.size)
+        assertEquals(1ValueSuffix, map[1KeySuffix])
+    }
+
+    @Test
+    fun replaceExistingKey() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+        map[1KeySuffix] = 2ValueSuffix
+
+        assertEquals(1, map.size)
+        assertEquals(2ValueSuffix, map[1KeySuffix])
+    }
+
+    @Test
+    fun put() {
+        val map = MutablePKeyPValueMap()
+
+        map.put(1KeySuffix, 1ValueSuffix)
+        assertEquals(1ValueSuffix, map[1KeySuffix])
+        map.put(1KeySuffix, 2ValueSuffix)
+        assertEquals(2ValueSuffix, map[1KeySuffix])
+    }
+
+    @Test
+    fun putAllArray() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+        map[2KeySuffix] = 2ValueSuffix
+
+        map.putAll(arrayOf(3KeySuffix to 3ValueSuffix, 7KeySuffix to 7ValueSuffix))
+
+        assertEquals(4, map.size)
+        assertEquals(3ValueSuffix, map[3KeySuffix])
+        assertEquals(7ValueSuffix, map[7KeySuffix])
+    }
+
+    @Test
+    fun plus() {
+        val map = MutablePKeyPValueMap()
+        map += 1KeySuffix to 1ValueSuffix
+
+        assertEquals(1, map.size)
+        assertEquals(1ValueSuffix, map[1KeySuffix])
+    }
+
+    @Test
+    fun plusArray() {
+        val map = MutablePKeyPValueMap()
+        map += arrayOf(3KeySuffix to 3ValueSuffix, 7KeySuffix to 7ValueSuffix)
+
+        assertEquals(2, map.size)
+        assertEquals(3ValueSuffix, map[3KeySuffix])
+        assertEquals(7ValueSuffix, map[7KeySuffix])
+    }
+
+    @Test
+    fun findNonExistingKey() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+
+        assertFailsWith<NoSuchElementException> {
+            map[2KeySuffix]
+        }
+    }
+
+    @Test
+    fun getOrDefault() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+
+        assertEquals(2ValueSuffix, map.getOrDefault(2KeySuffix, 2ValueSuffix))
+    }
+
+    @Test
+    fun getOrElse() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+
+        assertEquals(3ValueSuffix, map.getOrElse(3KeySuffix) { 3ValueSuffix })
+    }
+
+    @Test
+    fun getOrPut() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+
+        var counter = 0
+        map.getOrPut(1KeySuffix) {
+            counter++
+            2ValueSuffix
+        }
+        assertEquals(1ValueSuffix, map[1KeySuffix])
+        assertEquals(0, counter)
+
+        map.getOrPut(2KeySuffix) {
+            counter++
+            2ValueSuffix
+        }
+        assertEquals(2ValueSuffix, map[2KeySuffix])
+        assertEquals(1, counter)
+
+        map.getOrPut(2KeySuffix) {
+            counter++
+            3ValueSuffix
+        }
+        assertEquals(2ValueSuffix, map[2KeySuffix])
+        assertEquals(1, counter)
+
+        map.getOrPut(3KeySuffix) {
+            counter++
+            3ValueSuffix
+        }
+        assertEquals(3ValueSuffix, map[3KeySuffix])
+        assertEquals(2, counter)
+    }
+
+    @Test
+    fun remove() {
+        val map = MutablePKeyPValueMap()
+        map.remove(1KeySuffix)
+
+        map[1KeySuffix] = 1ValueSuffix
+        map.remove(1KeySuffix)
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val map = MutablePKeyPValueMap(6)
+        map[1KeySuffix] = 1ValueSuffix
+        map[2KeySuffix] = 2ValueSuffix
+        map[3KeySuffix] = 3ValueSuffix
+        map[4KeySuffix] = 4ValueSuffix
+        map[5KeySuffix] = 5ValueSuffix
+        map[6KeySuffix] = 6ValueSuffix
+
+        // Removing all the entries will mark the medata as deleted
+        map.remove(1KeySuffix)
+        map.remove(2KeySuffix)
+        map.remove(3KeySuffix)
+        map.remove(4KeySuffix)
+        map.remove(5KeySuffix)
+        map.remove(6KeySuffix)
+
+        assertEquals(0, map.size)
+
+        val capacity = map.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        map[7KeySuffix] = 7ValueSuffix
+
+        assertEquals(1, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun removeIf() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+        map[2KeySuffix] = 2ValueSuffix
+        map[3KeySuffix] = 3ValueSuffix
+        map[4KeySuffix] = 4ValueSuffix
+        map[5KeySuffix] = 5ValueSuffix
+        map[6KeySuffix] = 6ValueSuffix
+
+        map.removeIf { key, _ -> key == 1KeySuffix || key == 3KeySuffix }
+
+        assertEquals(4, map.size)
+        assertEquals(2ValueSuffix, map[2KeySuffix])
+        assertEquals(4ValueSuffix, map[4KeySuffix])
+        assertEquals(5ValueSuffix, map[5KeySuffix])
+        assertEquals(6ValueSuffix, map[6KeySuffix])
+    }
+
+    @Test
+    fun minus() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+        map[2KeySuffix] = 2ValueSuffix
+        map[3KeySuffix] = 3ValueSuffix
+
+        map -= 1KeySuffix
+
+        assertEquals(2, map.size)
+        assertFalse(1KeySuffix in map)
+    }
+
+    @Test
+    fun minusArray() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+        map[2KeySuffix] = 2ValueSuffix
+        map[3KeySuffix] = 3ValueSuffix
+
+        map -= pKeyArrayOf(3KeySuffix, 2KeySuffix)
+
+        assertEquals(1, map.size)
+        assertFalse(3KeySuffix in map)
+        assertFalse(2KeySuffix in map)
+    }
+
+    @Test
+    fun minusSet() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+        map[2KeySuffix] = 2ValueSuffix
+        map[3KeySuffix] = 3ValueSuffix
+
+        map -= pKeySetOf(3KeySuffix, 2KeySuffix)
+
+        assertEquals(1, map.size)
+        assertFalse(3KeySuffix in map)
+        assertFalse(2KeySuffix in map)
+    }
+
+    @Test
+    fun minusList() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+        map[2KeySuffix] = 2ValueSuffix
+        map[3KeySuffix] = 3ValueSuffix
+
+        map -= pKeyListOf(3KeySuffix, 2KeySuffix)
+
+        assertEquals(1, map.size)
+        assertFalse(3KeySuffix in map)
+        assertFalse(2KeySuffix in map)
+    }
+
+    @Test
+    fun conditionalRemove() {
+        val map = MutablePKeyPValueMap()
+        assertFalse(map.remove(1KeySuffix, 1ValueSuffix))
+
+        map[1KeySuffix] = 1ValueSuffix
+        assertTrue(map.remove(1KeySuffix, 1ValueSuffix))
+        assertEquals(0, map.size)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val map = MutablePKeyPValueMap()
+
+        for (i in 0 until 1700) {
+            map[i.toPKey()] = i.toPValue()
+        }
+
+        assertEquals(1700, map.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val map = MutablePKeyPValueMap()
+
+            for (j in 0 until i) {
+                map[j.toPKey()] = j.toPValue()
+            }
+
+            var counter = 0
+            map.forEach { key, value ->
+                assertEquals(key, value.toPKey())
+                counter++
+            }
+
+            assertEquals(i, counter)
+        }
+    }
+
+    @Test
+    fun forEachKey() {
+        for (i in 0..48) {
+            val map = MutablePKeyPValueMap()
+
+            for (j in 0 until i) {
+                map[j.toPKey()] = j.toPValue()
+            }
+
+            var counter = 0
+            val keys = BooleanArray(map.size)
+            map.forEachKey { key ->
+                keys[key.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            keys.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun forEachValue() {
+        for (i in 0..48) {
+            val map = MutablePKeyPValueMap()
+
+            for (j in 0 until i) {
+                map[j.toPKey()] = j.toPValue()
+            }
+
+            var counter = 0
+            val values = BooleanArray(map.size)
+            map.forEachValue { value ->
+                values[value.toInt()] = true
+                counter++
+            }
+
+            assertEquals(i, counter)
+            values.forEach { assertTrue(it) }
+        }
+    }
+
+    @Test
+    fun clear() {
+        val map = MutablePKeyPValueMap()
+
+        for (i in 0 until 32) {
+            map[i.toPKey()] = i.toPValue()
+        }
+
+        val capacity = map.capacity
+        map.clear()
+
+        assertEquals(0, map.size)
+        assertEquals(capacity, map.capacity)
+    }
+
+    @Test
+    fun string() {
+        val map = MutablePKeyPValueMap()
+        assertEquals("{}", map.toString())
+
+        map[1KeySuffix] = 1ValueSuffix
+        map[2KeySuffix] = 2ValueSuffix
+        val oneValueString = 1ValueSuffix.toString()
+        val twoValueString = 2ValueSuffix.toString()
+        val oneKeyString = 1KeySuffix.toString()
+        val twoKeyString = 2KeySuffix.toString()
+        assertTrue(
+            "{$oneKeyString=$oneValueString, $twoKeyString=$twoValueString}" == map.toString() ||
+                "{$twoKeyString=$twoValueString, $oneKeyString=$oneValueString}" == map.toString()
+        )
+    }
+
+    @Test
+    fun equals() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+
+        assertFalse(map.equals(null))
+        assertEquals(map, map)
+
+        val map2 = MutablePKeyPValueMap()
+        assertNotEquals(map, map2)
+
+        map2[1KeySuffix] = 1ValueSuffix
+        assertEquals(map, map2)
+    }
+
+    @Test
+    fun containsKey() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+
+        assertTrue(map.containsKey(1KeySuffix))
+        assertFalse(map.containsKey(2KeySuffix))
+    }
+
+    @Test
+    fun contains() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+
+        assertTrue(1KeySuffix in map)
+        assertFalse(2KeySuffix in map)
+    }
+
+    @Test
+    fun containsValue() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+
+        assertTrue(map.containsValue(1ValueSuffix))
+        assertFalse(map.containsValue(3ValueSuffix))
+    }
+
+    @Test
+    fun empty() {
+        val map = MutablePKeyPValueMap()
+        assertTrue(map.isEmpty())
+        assertFalse(map.isNotEmpty())
+        assertTrue(map.none())
+        assertFalse(map.any())
+
+        map[1KeySuffix] = 1ValueSuffix
+
+        assertFalse(map.isEmpty())
+        assertTrue(map.isNotEmpty())
+        assertTrue(map.any())
+        assertFalse(map.none())
+    }
+
+    @Test
+    fun count() {
+        val map = MutablePKeyPValueMap()
+        assertEquals(0, map.count())
+
+        map[1KeySuffix] = 1ValueSuffix
+        assertEquals(1, map.count())
+
+        map[2KeySuffix] = 2ValueSuffix
+        map[3KeySuffix] = 3ValueSuffix
+        map[4KeySuffix] = 4ValueSuffix
+        map[5KeySuffix] = 5ValueSuffix
+        map[6KeySuffix] = 6ValueSuffix
+
+        assertEquals(2, map.count { key, _ -> key <= 2KeySuffix })
+        assertEquals(0, map.count { key, _ -> key < 0KeySuffix })
+    }
+
+    @Test
+    fun any() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+        map[2KeySuffix] = 2ValueSuffix
+        map[3KeySuffix] = 3ValueSuffix
+        map[4KeySuffix] = 4ValueSuffix
+        map[5KeySuffix] = 5ValueSuffix
+        map[6KeySuffix] = 6ValueSuffix
+
+        assertTrue(map.any { key, _ -> key == 4KeySuffix })
+        assertFalse(map.any { key, _ -> key < 0KeySuffix })
+    }
+
+    @Test
+    fun all() {
+        val map = MutablePKeyPValueMap()
+        map[1KeySuffix] = 1ValueSuffix
+        map[2KeySuffix] = 2ValueSuffix
+        map[3KeySuffix] = 3ValueSuffix
+        map[4KeySuffix] = 4ValueSuffix
+        map[5KeySuffix] = 5ValueSuffix
+        map[6KeySuffix] = 6ValueSuffix
+
+        assertTrue(map.all { key, value -> key > 0KeySuffix && value >= 1ValueSuffix })
+        assertFalse(map.all { key, _ -> key < 6KeySuffix })
+    }
+
+    @Test
+    fun trim() {
+        val map = MutablePKeyPValueMap()
+        assertEquals(7, map.trim())
+
+        map[1KeySuffix] = 1ValueSuffix
+        map[3KeySuffix] = 3ValueSuffix
+
+        assertEquals(0, map.trim())
+
+        for (i in 0 until 1700) {
+            map[i.toPKey()] = i.toPValue()
+        }
+
+        assertEquals(2047, map.capacity)
+
+        // After removing these items, our capacity needs should go
+        // from 2047 down to 1023
+        for (i in 0 until 1700) {
+            if (i and 0x1 == 0x0) {
+                val s = i.toPKey()
+                map.remove(s)
+            }
+        }
+
+        assertEquals(1024, map.trim())
+        assertEquals(0, map.trim())
+    }
+}
diff --git a/collection/collection/template/PKeySet.kt.template b/collection/collection/template/PKeySet.kt.template
new file mode 100644
index 0000000..f950f03
--- /dev/null
+++ b/collection/collection/template/PKeySet.kt.template
@@ -0,0 +1,784 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "KotlinRedundantDiagnosticSuppress",
+    "KotlinConstantConditions",
+    "PropertyName",
+    "ConstPropertyName",
+    "PrivatePropertyName",
+    "NOTHING_TO_INLINE"
+)
+
+package androidx.collection
+
+import kotlin.contracts.contract
+import kotlin.jvm.JvmField
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+// This is a copy of ScatterSet, but with primitive elements
+
+// Default empty set to avoid allocations
+private val EmptyPKeySet = MutablePKeySet(0)
+
+// An empty array of pKeys
+internal val EmptyPKeyArray = PKeyArray(0)
+
+/**
+ * Returns an empty, read-only [PKeySet].
+ */
+public fun emptyPKeySet(): PKeySet = EmptyPKeySet
+
+/**
+ * Returns an empty, read-only [ScatterSet].
+ */
+@Suppress("UNCHECKED_CAST")
+public fun pKeySetOf(): PKeySet = EmptyPKeySet
+
+/**
+ * Returns a new read-only [PKeySet] with only [element1] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun pKeySetOf(element1: PKey): PKeySet = mutablePKeySetOf(element1)
+
+/**
+ * Returns a new read-only [PKeySet] with only [element1] and [element2] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun pKeySetOf(element1: PKey, element2: PKey): PKeySet =
+    mutablePKeySetOf(element1, element2)
+
+/**
+ * Returns a new read-only [PKeySet] with only [element1], [element2], and [element3] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun pKeySetOf(element1: PKey, element2: PKey, element3: PKey): PKeySet =
+    mutablePKeySetOf(element1, element2, element3)
+
+/**
+ * Returns a new read-only [PKeySet] with only [elements] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+public fun pKeySetOf(vararg elements: PKey): PKeySet =
+    MutablePKeySet(elements.size).apply { plusAssign(elements) }
+
+/**
+ * Returns a new [MutablePKeySet].
+ */
+public fun mutablePKeySetOf(): MutablePKeySet = MutablePKeySet()
+
+/**
+ * Returns a new [MutablePKeySet] with only [element1] in it.
+ */
+public fun mutablePKeySetOf(element1: PKey): MutablePKeySet =
+    MutablePKeySet(1).apply {
+        plusAssign(element1)
+    }
+
+/**
+ * Returns a new [MutablePKeySet] with only [element1] and [element2] in it.
+ */
+public fun mutablePKeySetOf(element1: PKey, element2: PKey): MutablePKeySet =
+    MutablePKeySet(2).apply {
+        plusAssign(element1)
+        plusAssign(element2)
+    }
+
+/**
+ * Returns a new [MutablePKeySet] with only [element1], [element2], and [element3] in it.
+ */
+public fun mutablePKeySetOf(element1: PKey, element2: PKey, element3: PKey): MutablePKeySet =
+    MutablePKeySet(3).apply {
+        plusAssign(element1)
+        plusAssign(element2)
+        plusAssign(element3)
+    }
+
+/**
+ * Returns a new [MutablePKeySet] with the specified elements.
+ */
+public fun mutablePKeySetOf(vararg elements: PKey): MutablePKeySet =
+    MutablePKeySet(elements.size).apply { plusAssign(elements) }
+
+/**
+ * [PKeySet] is a container with a [Set]-like interface designed to avoid
+ * allocations, including boxing.
+ *
+ * This implementation makes no guarantee as to the order of the elements,
+ * nor does it make guarantees that the order remains constant over time.
+ *
+ * Though [PKeySet] offers a read-only interface, it is always backed
+ * by a [MutablePKeySet]. Read operations alone are thread-safe. However,
+ * any mutations done through the backing [MutablePKeySet] while reading
+ * on another thread are not safe and the developer must protect the set
+ * from such changes during read operations.
+ *
+ * @see [MutablePKeySet]
+ */
+public sealed class PKeySet {
+    // NOTE: Our arrays are marked internal to implement inlined forEach{}
+    // The backing array for the metadata bytes contains
+    // `capacity + 1 + ClonedMetadataCount` elements, including when
+    // the set is empty (see [EmptyGroup]).
+    @PublishedApi
+    @JvmField
+    internal var metadata: LongArray = EmptyGroup
+
+    @PublishedApi
+    @JvmField
+    internal var elements: PKeyArray = EmptyPKeyArray
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the capacity
+    @JvmField
+    internal var _capacity: Int = 0
+
+    /**
+     * Returns the number of elements that can be stored in this set
+     * without requiring internal storage reallocation.
+     */
+    @get:androidx.annotation.IntRange(from = 0)
+    public val capacity: Int
+        get() = _capacity
+
+    // We use a backing field for capacity to avoid invokevirtual calls
+    // every time we need to look at the size
+    @JvmField
+    internal var _size: Int = 0
+
+    /**
+     * Returns the number of elements in this set.
+     */
+    @get:androidx.annotation.IntRange(from = 0)
+    public val size: Int
+        get() = _size
+
+    /**
+     * Returns `true` if this set has at least one element.
+     */
+    public fun any(): Boolean = _size != 0
+
+    /**
+     * Returns `true` if this set has no elements.
+     */
+    public fun none(): Boolean = _size == 0
+
+    /**
+     * Indicates whether this set is empty.
+     */
+    public fun isEmpty(): Boolean = _size == 0
+
+    /**
+     * Returns `true` if this set is not empty.
+     */
+    public fun isNotEmpty(): Boolean = _size != 0
+
+    /**
+     * Returns the first element in the collection.
+     * @throws NoSuchElementException if the collection is empty
+     */
+    public inline fun first(): PKey {
+        forEach { return it }
+        throw NoSuchElementException("The PKeySet is empty")
+    }
+
+    /**
+     * Returns the first element in the collection for which [predicate] returns `true`.
+     *
+     * **Note** There is no mechanism for both determining if there is an element that matches
+     * [predicate] _and_ returning it if it exists. Developers should use [forEach] to achieve
+     * this behavior.
+     *
+     * @param predicate Called on elements of the set, returning `true` for an element that matches
+     * or `false` if it doesn't
+     * @return An element in the set for which [predicate] returns `true`.
+     * @throws NoSuchElementException if [predicate] returns `false` for all elements or the
+     * collection is empty.
+     */
+    public inline fun first(predicate: (element: PKey) -> Boolean): PKey {
+        contract { callsInPlace(predicate) }
+        forEach { if (predicate(it)) return it }
+        throw NoSuchElementException("Could not find a match")
+    }
+
+    /**
+     * Iterates over every element stored in this set by invoking
+     * the specified [block] lambda.
+     */
+    @PublishedApi
+    internal inline fun forEachIndex(block: (index: Int) -> Unit) {
+        contract { callsInPlace(block) }
+        val m = metadata
+        val lastIndex = m.size - 2 // We always have 0 or at least 2 elements
+
+        for (i in 0..lastIndex) {
+            var slot = m[i]
+            if (slot.maskEmptyOrDeleted() != BitmaskMsb) {
+                // Branch-less if (i == lastIndex) 7 else 8
+                // i - lastIndex returns a negative value when i < lastIndex,
+                // so 1 is set as the MSB. By inverting and shifting we get
+                // 0 when i < lastIndex, 1 otherwise.
+                val bitCount = 8 - ((i - lastIndex).inv() ushr 31)
+                for (j in 0 until bitCount) {
+                    if (isFull(slot and 0xFFL)) {
+                        val index = (i shl 3) + j
+                        block(index)
+                    }
+                    slot = slot shr 8
+                }
+                if (bitCount != 8) return
+            }
+        }
+    }
+
+    /**
+     * Iterates over every element stored in this set by invoking
+     * the specified [block] lambda.
+     * @param block called with each element in the set
+     */
+    public inline fun forEach(block: (element: PKey) -> Unit) {
+        contract { callsInPlace(block) }
+        val k = elements
+
+        forEachIndex { index ->
+            block(k[index])
+        }
+    }
+
+    /**
+     * Returns true if all elements match the given [predicate].
+     * @param predicate called for elements in the set to determine if it returns return `true` for
+     * all elements.
+     */
+    public inline fun all(predicate: (element: PKey) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        forEach { element ->
+            if (!predicate(element)) return false
+        }
+        return true
+    }
+
+    /**
+     * Returns true if at least one element matches the given [predicate].
+     * @param predicate called for elements in the set to determine if it returns `true` for any
+     * elements.
+     */
+    public inline fun any(predicate: (element: PKey) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        forEach { element ->
+            if (predicate(element)) return true
+        }
+        return false
+    }
+
+    /**
+     * Returns the number of elements in this set.
+     */
+    @androidx.annotation.IntRange(from = 0)
+    public fun count(): Int = _size
+
+    /**
+     * Returns the number of elements matching the given [predicate].
+     * @param predicate Called for all elements in the set to count the number for which it returns
+     * `true`.
+     */
+    @androidx.annotation.IntRange(from = 0)
+    public inline fun count(predicate: (element: PKey) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        var count = 0
+        forEach { element ->
+            if (predicate(element)) count++
+        }
+        return count
+    }
+
+    /**
+     * Returns `true` if the specified [element] is present in this set, `false`
+     * otherwise.
+     * @param element The element to look for in this set
+     */
+    public operator fun contains(element: PKey): Boolean = findElementIndex(element) >= 0
+
+    /**
+     * Returns the hash code value for this set. The hash code of a set is defined to be the
+     * sum of the hash codes of the elements in the set.
+     */
+    public override fun hashCode(): Int {
+        var hash = 0
+
+        forEach { element ->
+            hash += element.hashCode()
+        }
+
+        return hash
+    }
+
+    /**
+     * Compares the specified object [other] with this set for equality.
+     * The two objects are considered equal if [other]:
+     * - Is a [PKeySet]
+     * - Has the same [size] as this set
+     * - Contains elements equal to this set's elements
+     */
+    public override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
+        }
+
+        if (other !is PKeySet) {
+            return false
+        }
+        if (other._size != _size) {
+            return false
+        }
+
+        forEach { element ->
+            if (element !in other) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns a string representation of this set. The set is denoted in the
+     * string by the `{}`. Each element is separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "[]"
+        }
+
+        val s = StringBuilder().append('[')
+        val last = _size - 1
+        var index = 0
+        forEach { element ->
+            s.append(element)
+            if (index++ < last) {
+                s.append(',').append(' ')
+            }
+        }
+
+        return s.append(']').toString()
+    }
+
+    /**
+     * Scans the set to find the index in the backing arrays of the
+     * specified [element]. Returns -1 if the element is not present.
+     */
+    internal inline fun findElementIndex(element: PKey): Int {
+        val hash = hash(element)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = h1(hash) and probeMask
+        var probeIndex = 0
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (elements[index] == element) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        return -1
+    }
+}
+
+/**
+ * [MutablePKeySet] is a container with a [MutableSet]-like interface based on a flat
+ * hash table implementation. The underlying implementation is designed to avoid
+ * all allocations on insertion, removal, retrieval, and iteration. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added elements to the set.
+ *
+ * This implementation makes no guarantee as to the order of the elements stored,
+ * nor does it make guarantees that the order remains constant over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the set (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Concurrent reads are however safe.
+ *
+ * @constructor Creates a new [MutablePKeySet]
+ * @param initialCapacity The initial desired capacity for this container.
+ * The container will honor this value by guaranteeing its internal structures
+ * can hold that many elements without requiring any allocations. The initial
+ * capacity can be set to 0.
+ */
+public class MutablePKeySet(
+    initialCapacity: Int = DefaultScatterCapacity
+) : PKeySet() {
+    // Number of elements we can add before we need to grow
+    private var growthLimit = 0
+
+    init {
+        require(initialCapacity >= 0) { "Capacity must be a positive value." }
+        initializeStorage(unloadedCapacity(initialCapacity))
+    }
+
+    private fun initializeStorage(initialCapacity: Int) {
+        val newCapacity = if (initialCapacity > 0) {
+            // Since we use longs for storage, our capacity is never < 7, enforce
+            // it here. We do have a special case for 0 to create small empty maps
+            maxOf(7, normalizeCapacity(initialCapacity))
+        } else {
+            0
+        }
+        _capacity = newCapacity
+        initializeMetadata(newCapacity)
+        elements = PKeyArray(newCapacity)
+    }
+
+    private fun initializeMetadata(capacity: Int) {
+        metadata = if (capacity == 0) {
+            EmptyGroup
+        } else {
+            // Round up to the next multiple of 8 and find how many longs we need
+            val size = (((capacity + 1 + ClonedMetadataCount) + 7) and 0x7.inv()) shr 3
+            LongArray(size).apply {
+                fill(AllEmpty)
+            }
+        }
+        writeRawMetadata(metadata, capacity, Sentinel)
+        initializeGrowth()
+    }
+
+    private fun initializeGrowth() {
+        growthLimit = loadedCapacity(capacity) - _size
+    }
+
+    /**
+     * Adds the specified element to the set.
+     * @param element The element to add to the set.
+     * @return `true` if the element has been added or `false` if the element is already
+     * contained within the set.
+     */
+    public fun add(element: PKey): Boolean {
+        val oldSize = _size
+        val index = findAbsoluteInsertIndex(element)
+        elements[index] = element
+        return _size != oldSize
+    }
+
+    /**
+     * Adds the specified element to the set.
+     * @param element The element to add to the set.
+     */
+    public operator fun plusAssign(element: PKey) {
+        val index = findAbsoluteInsertIndex(element)
+        elements[index] = element
+    }
+
+    /**
+     * Adds all the [elements] into this set.
+     * @param elements An array of elements to add to the set.
+     * @return `true` if any of the specified elements were added to the collection,
+     * `false` if the collection was not modified.
+     */
+    public fun addAll(@Suppress("ArrayReturn") elements: PKeyArray): Boolean {
+        val oldSize = _size
+        plusAssign(elements)
+        return oldSize != _size
+    }
+
+    /**
+     * Adds all the [elements] into this set.
+     * @param elements An array of elements to add to the set.
+     */
+    public operator fun plusAssign(@Suppress("ArrayReturn") elements: PKeyArray) {
+        elements.forEach { element ->
+            plusAssign(element)
+        }
+    }
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     * @param elements A [PKeySet] of elements to add to this set.
+     * @return `true` if any of the specified elements were added to the collection,
+     * `false` if the collection was not modified.
+     */
+    public fun addAll(elements: PKeySet): Boolean {
+        val oldSize = _size
+        plusAssign(elements)
+        return oldSize != _size
+    }
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     * @param elements A [PKeySet] of elements to add to this set.
+     */
+    public operator fun plusAssign(elements: PKeySet) {
+        elements.forEach { element ->
+            plusAssign(element)
+        }
+    }
+
+    /**
+     * Removes the specified [element] from the set.
+     * @param element The element to remove from the set.
+     * @return `true` if the [element] was present in the set, or `false` if it wasn't
+     * present before removal.
+     */
+    public fun remove(element: PKey): Boolean {
+        val index = findElementIndex(element)
+        val exists = index >= 0
+        if (exists) {
+            removeElementAt(index)
+        }
+        return exists
+    }
+
+    /**
+     * Removes the specified [element] from the set if it is present.
+     * @param element The element to remove from the set.
+     */
+    public operator fun minusAssign(element: PKey) {
+        val index = findElementIndex(element)
+        if (index >= 0) {
+            removeElementAt(index)
+        }
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     * @param elements An array of elements to be removed from the set.
+     * @return `true` if the set was changed or `false` if none of the elements were present.
+     */
+    public fun removeAll(@Suppress("ArrayReturn") elements: PKeyArray): Boolean {
+        val oldSize = _size
+        minusAssign(elements)
+        return oldSize != _size
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     * @param elements An array of elements to be removed from the set.
+     */
+    public operator fun minusAssign(@Suppress("ArrayReturn") elements: PKeyArray) {
+        elements.forEach { element ->
+            minusAssign(element)
+        }
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     * @param elements An [PKeySet] of elements to be removed from the set.
+     * @return `true` if the set was changed or `false` if none of the elements were present.
+     */
+    public fun removeAll(elements: PKeySet): Boolean {
+        val oldSize = _size
+        minusAssign(elements)
+        return oldSize != _size
+    }
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     * @param elements An [PKeySet] of elements to be removed from the set.
+     */
+    public operator fun minusAssign(elements: PKeySet) {
+        elements.forEach { element ->
+            minusAssign(element)
+        }
+    }
+
+    private fun removeElementAt(index: Int) {
+        _size -= 1
+
+        // TODO: We could just mark the element as empty if there's a group
+        //       window around this element that was already empty
+        writeMetadata(index, Deleted)
+    }
+
+    /**
+     * Removes all elements from this set.
+     */
+    public fun clear() {
+        _size = 0
+        if (metadata !== EmptyGroup) {
+            metadata.fill(AllEmpty)
+            writeRawMetadata(metadata, _capacity, Sentinel)
+        }
+        initializeGrowth()
+    }
+
+    /**
+     * Scans the set to find the index at which we can store a given [element].
+     * If the element already exists in the set, its index
+     * will be returned, otherwise the index of an empty slot will be returned.
+     * Calling this function may cause the internal storage to be reallocated
+     * if the set is full.
+     */
+    private fun findAbsoluteInsertIndex(element: PKey): Int {
+        val hash = hash(element)
+        val hash1 = h1(hash)
+        val hash2 = h2(hash)
+
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+
+        while (true) {
+            val g = group(metadata, probeOffset)
+            var m = g.match(hash2)
+            while (m.hasNext()) {
+                val index = (probeOffset + m.get()) and probeMask
+                if (elements[index] == element) {
+                    return index
+                }
+                m = m.next()
+            }
+
+            if (g.maskEmpty() != 0L) {
+                break
+            }
+
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+
+        var index = findFirstAvailableSlot(hash1)
+        if (growthLimit == 0 && !isDeleted(metadata, index)) {
+            adjustStorage()
+            index = findFirstAvailableSlot(hash1)
+        }
+
+        _size += 1
+        growthLimit -= if (isEmpty(metadata, index)) 1 else 0
+        writeMetadata(index, hash2.toLong())
+
+        return index
+    }
+
+    /**
+     * Finds the first empty or deleted slot in the set in which we can
+     * store a value without resizing the internal storage.
+     */
+    private fun findFirstAvailableSlot(hash1: Int): Int {
+        val probeMask = _capacity
+        var probeOffset = hash1 and probeMask
+        var probeIndex = 0
+        while (true) {
+            val g = group(metadata, probeOffset)
+            val m = g.maskEmptyOrDeleted()
+            if (m != 0L) {
+                return (probeOffset + m.lowestBitSet()) and probeMask
+            }
+            probeIndex += GroupWidth
+            probeOffset = (probeOffset + probeIndex) and probeMask
+        }
+    }
+
+    /**
+     * Trims this [MutablePKeySet]'s storage so it is sized appropriately
+     * to hold the current elements.
+     *
+     * Returns the number of empty elements removed from this set's storage.
+     * Returns 0 if no trimming is necessary or possible.
+     */
+    @androidx.annotation.IntRange(from = 0)
+    public fun trim(): Int {
+        val previousCapacity = _capacity
+        val newCapacity = normalizeCapacity(unloadedCapacity(_size))
+        if (newCapacity < previousCapacity) {
+            resizeStorage(newCapacity)
+            return previousCapacity - _capacity
+        }
+        return 0
+    }
+
+    /**
+     * Grow internal storage if necessary. This function can instead opt to
+     * remove deleted elements from the set to avoid an expensive reallocation
+     * of the underlying storage. This "rehash in place" occurs when the
+     * current size is <= 25/32 of the set capacity. The choice of 25/32 is
+     * detailed in the implementation of abseil's `raw_hash_map`.
+     */
+    private fun adjustStorage() {
+        if (_capacity > GroupWidth && _size.toULong() * 32UL <= _capacity.toULong() * 25UL) {
+            // TODO: Avoid resize and drop deletes instead
+            resizeStorage(nextCapacity(_capacity))
+        } else {
+            resizeStorage(nextCapacity(_capacity))
+        }
+    }
+
+    private fun resizeStorage(newCapacity: Int) {
+        val previousMetadata = metadata
+        val previousElements = elements
+        val previousCapacity = _capacity
+
+        initializeStorage(newCapacity)
+
+        val newElements = elements
+
+        for (i in 0 until previousCapacity) {
+            if (isFull(previousMetadata, i)) {
+                val previousElement = previousElements[i]
+                val hash = hash(previousElement)
+                val index = findFirstAvailableSlot(h1(hash))
+
+                writeMetadata(index, h2(hash).toLong())
+                newElements[index] = previousElement
+            }
+        }
+    }
+
+    /**
+     * Writes the "H2" part of an entry into the metadata array at the specified
+     * [index]. The index must be a valid index. This function ensures the
+     * metadata is also written in the clone area at the end.
+     */
+    private inline fun writeMetadata(index: Int, value: Long) {
+        val m = metadata
+        writeRawMetadata(m, index, value)
+
+        // Mirroring
+        val c = _capacity
+        val cloneIndex = ((index - ClonedMetadataCount) and c) +
+            (ClonedMetadataCount and c)
+        writeRawMetadata(m, cloneIndex, value)
+    }
+}
+
+/**
+ * Returns the hash code of [k]. This follows the [HashSet] default behavior on Android
+ * of returning [Object.hashcode()] with the higher bits of hash spread to the lower bits.
+ */
+internal inline fun hash(k: PKey): Int {
+    val hash = k.hashCode()
+    return hash xor (hash ushr 16)
+}
diff --git a/collection/collection/template/PKeySetTest.kt.template b/collection/collection/template/PKeySetTest.kt.template
new file mode 100644
index 0000000..cd5a422
--- /dev/null
+++ b/collection/collection/template/PKeySetTest.kt.template
@@ -0,0 +1,534 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.collection
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// DO NOT MAKE CHANGES to the kotlin source file.
+//
+// This file was generated from a template in the template directory.
+// Make a change to the original template and run the generateCollections.sh script
+// to ensure the change is available on all versions of the map.
+// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+class PKeySetTest {
+    @Test
+    fun emptyPKeySetConstructor() {
+        val set = MutablePKeySet()
+        assertEquals(7, set.capacity)
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun immutableEmptyPKeySet() {
+        val set: PKeySet = emptyPKeySet()
+        assertEquals(0, set.capacity)
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun zeroCapacityPKeySet() {
+        val set = MutablePKeySet(0)
+        assertEquals(0, set.capacity)
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun emptyPKeySetWithCapacity() {
+        // When unloading the suggested capacity, we'll fall outside of the
+        // expected bucket of 2047 entries, and we'll get 4095 instead
+        val set = MutablePKeySet(1800)
+        assertEquals(4095, set.capacity)
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun mutablePKeySetBuilder() {
+        val empty = mutablePKeySetOf()
+        assertEquals(0, empty.size)
+
+        val withElements = mutablePKeySetOf(1KeySuffix, 2KeySuffix)
+        assertEquals(2, withElements.size)
+        assertTrue(1KeySuffix in withElements)
+        assertTrue(2KeySuffix in withElements)
+    }
+
+    @Test
+    fun addToPKeySet() {
+        val set = MutablePKeySet()
+        set += 1KeySuffix
+        assertTrue(set.add(2KeySuffix))
+
+        assertEquals(2, set.size)
+        val elements = PKeyArray(2)
+        var index = 0
+        set.forEach { element ->
+            elements[index++] = element
+        }
+        elements.sort()
+        assertEquals(1KeySuffix, elements[0])
+        assertEquals(2KeySuffix, elements[1])
+    }
+
+    @Test
+    fun addToSizedPKeySet() {
+        val set = MutablePKeySet(12)
+        set += 1KeySuffix
+
+        assertEquals(1, set.size)
+        assertEquals(1KeySuffix, set.first())
+    }
+
+    @Test
+    fun addExistingElement() {
+        val set = MutablePKeySet(12)
+        set += 1KeySuffix
+        assertFalse(set.add(1KeySuffix))
+        set += 1KeySuffix
+
+        assertEquals(1, set.size)
+        assertEquals(1KeySuffix, set.first())
+    }
+
+    @Test
+    fun addAllArray() {
+        val set = mutablePKeySetOf(1KeySuffix)
+        assertFalse(set.addAll(pKeyArrayOf(1KeySuffix)))
+        assertEquals(1, set.size)
+        assertTrue(set.addAll(pKeyArrayOf(1KeySuffix, 2KeySuffix)))
+        assertEquals(2, set.size)
+        assertTrue(2KeySuffix in set)
+    }
+
+    @Test
+    fun addAllPKeySet() {
+        val set = mutablePKeySetOf(1KeySuffix)
+        assertFalse(set.addAll(mutablePKeySetOf(1KeySuffix)))
+        assertEquals(1, set.size)
+        assertTrue(set.addAll(mutablePKeySetOf(1KeySuffix, 2KeySuffix)))
+        assertEquals(2, set.size)
+        assertTrue(2KeySuffix in set)
+    }
+
+    @Test
+    fun plusAssignArray() {
+        val set = mutablePKeySetOf(1KeySuffix)
+        set += pKeyArrayOf(1KeySuffix)
+        assertEquals(1, set.size)
+        set += pKeyArrayOf(1KeySuffix, 2KeySuffix)
+        assertEquals(2, set.size)
+        assertTrue(2KeySuffix in set)
+    }
+
+    @Test
+    fun plusAssignPKeySet() {
+        val set = mutablePKeySetOf(1KeySuffix)
+        set += mutablePKeySetOf(1KeySuffix)
+        assertEquals(1, set.size)
+        set += mutablePKeySetOf(1KeySuffix, 2KeySuffix)
+        assertEquals(2, set.size)
+        assertTrue(2KeySuffix in set)
+    }
+
+    @Test
+    fun firstWithValue() {
+        val set = MutablePKeySet()
+        set += 1KeySuffix
+        set += 2KeySuffix
+        var element: PKey = -1KeySuffix
+        var otherElement: PKey = -1KeySuffix
+        set.forEach { if (element == -1KeySuffix) element = it else otherElement = it }
+        assertEquals(element, set.first())
+        set -= element
+        assertEquals(otherElement, set.first())
+    }
+
+    @Test
+    fun firstEmpty() {
+        assertFailsWith(NoSuchElementException::class) {
+            val set = MutablePKeySet()
+            set.first()
+        }
+    }
+
+    @Test
+    fun firstMatching() {
+        val set = MutablePKeySet()
+        set += 1KeySuffix
+        set += 2KeySuffix
+        assertEquals(1KeySuffix, set.first { it < 2KeySuffix })
+        assertEquals(2KeySuffix, set.first { it > 1KeySuffix })
+    }
+
+    @Test
+    fun firstMatchingEmpty() {
+        assertFailsWith(NoSuchElementException::class) {
+            val set = MutablePKeySet()
+            set.first { it > 0KeySuffix }
+        }
+    }
+
+    @Test
+    fun firstMatchingNoMatch() {
+        assertFailsWith(NoSuchElementException::class) {
+            val set = MutablePKeySet()
+            set += 1KeySuffix
+            set += 2KeySuffix
+            set.first { it < 0KeySuffix }
+        }
+    }
+
+    @Test
+    fun remove() {
+        val set = MutablePKeySet()
+        assertFalse(set.remove(1KeySuffix))
+
+        set += 1KeySuffix
+        assertTrue(set.remove(1KeySuffix))
+        assertEquals(0, set.size)
+
+        set += 1KeySuffix
+        set -= 1KeySuffix
+        assertEquals(0, set.size)
+    }
+
+    @Test
+    fun removeThenAdd() {
+        // Use a size of 6 to fit in a single entry in the metadata table
+        val set = MutablePKeySet(6)
+        set += 1KeySuffix
+        set += 5KeySuffix
+        set += 6KeySuffix
+        set += 9KeySuffix
+        set += 11KeySuffix
+        set += 13KeySuffix
+
+        // Removing all the entries will mark the medata as deleted
+        set.remove(1KeySuffix)
+        set.remove(5KeySuffix)
+        set.remove(6KeySuffix)
+        set.remove(9KeySuffix)
+        set.remove(11KeySuffix)
+        set.remove(13KeySuffix)
+
+        assertEquals(0, set.size)
+
+        val capacity = set.capacity
+
+        // Make sure reinserting an entry after filling the table
+        // with "Deleted" markers works
+        set += 3KeySuffix
+
+        assertEquals(1, set.size)
+        assertEquals(capacity, set.capacity)
+    }
+
+    @Test
+    fun removeAllArray() {
+        val set = mutablePKeySetOf(1KeySuffix, 2KeySuffix)
+        assertFalse(set.removeAll(pKeyArrayOf(3KeySuffix, 5KeySuffix)))
+        assertEquals(2, set.size)
+        assertTrue(set.removeAll(pKeyArrayOf(3KeySuffix, 1KeySuffix, 5KeySuffix)))
+        assertEquals(1, set.size)
+        assertFalse(1KeySuffix in set)
+    }
+
+    @Test
+    fun removeAllPKeySet() {
+        val set = mutablePKeySetOf(1KeySuffix, 2KeySuffix)
+        assertFalse(set.removeAll(mutablePKeySetOf(3KeySuffix, 5KeySuffix)))
+        assertEquals(2, set.size)
+        assertTrue(set.removeAll(mutablePKeySetOf(3KeySuffix, 1KeySuffix, 5KeySuffix)))
+        assertEquals(1, set.size)
+        assertFalse(1KeySuffix in set)
+    }
+
+    @Test
+    fun minusAssignArray() {
+        val set = mutablePKeySetOf(1KeySuffix, 2KeySuffix)
+        set -= pKeyArrayOf(3KeySuffix, 5KeySuffix)
+        assertEquals(2, set.size)
+        set -= pKeyArrayOf(3KeySuffix, 1KeySuffix, 5KeySuffix)
+        assertEquals(1, set.size)
+        assertFalse(1KeySuffix in set)
+    }
+
+    @Test
+    fun minusAssignPKeySet() {
+        val set = mutablePKeySetOf(1KeySuffix, 2KeySuffix)
+        set -= mutablePKeySetOf(3KeySuffix, 5KeySuffix)
+        assertEquals(2, set.size)
+        set -= mutablePKeySetOf(3KeySuffix, 1KeySuffix, 5KeySuffix)
+        assertEquals(1, set.size)
+        assertFalse(1KeySuffix in set)
+    }
+
+    @Test
+    fun insertManyEntries() {
+        val set = MutablePKeySet()
+
+        for (i in 0 until 1700) {
+            set += i.toPKey()
+        }
+
+        assertEquals(1700, set.size)
+    }
+
+    @Test
+    fun forEach() {
+        for (i in 0..48) {
+            val set = MutablePKeySet()
+
+            for (j in 0 until i) {
+                set += j.toPKey()
+            }
+
+            val elements = PKeyArray(i)
+            var index = 0
+            set.forEach { element ->
+                elements[index++] = element
+            }
+            elements.sort()
+
+            index = 0
+            elements.forEach { element ->
+                assertEquals(element, index.toPKey())
+                index++
+            }
+        }
+    }
+
+    @Test
+    fun clear() {
+        val set = MutablePKeySet()
+
+        for (i in 0 until 32) {
+            set += i.toPKey()
+        }
+
+        val capacity = set.capacity
+        set.clear()
+
+        assertEquals(0, set.size)
+        assertEquals(capacity, set.capacity)
+    }
+
+    @Test
+    fun string() {
+        val set = MutablePKeySet()
+        assertEquals("[]", set.toString())
+
+        set += 1KeySuffix
+        set += 5KeySuffix
+        assertTrue(
+            "[${1KeySuffix}, ${5KeySuffix}]" == set.toString() ||
+                "[${5KeySuffix}, ${1KeySuffix}]" == set.toString()
+        )
+    }
+
+    @Test
+    fun equals() {
+        val set = MutablePKeySet()
+        set += 1KeySuffix
+        set += 5KeySuffix
+
+        assertFalse(set.equals(null))
+        assertEquals(set, set)
+
+        val set2 = MutablePKeySet()
+        set2 += 5KeySuffix
+
+        assertNotEquals(set, set2)
+
+        set2 += 1KeySuffix
+        assertEquals(set, set2)
+    }
+
+    @Test
+    fun contains() {
+        val set = MutablePKeySet()
+        set += 1KeySuffix
+        set += 5KeySuffix
+
+        assertTrue(set.contains(1KeySuffix))
+        assertTrue(set.contains(5KeySuffix))
+        assertFalse(set.contains(2KeySuffix))
+    }
+
+    @Test
+    fun empty() {
+        val set = MutablePKeySet()
+        assertTrue(set.isEmpty())
+        assertFalse(set.isNotEmpty())
+        assertTrue(set.none())
+        assertFalse(set.any())
+
+        set += 1KeySuffix
+
+        assertFalse(set.isEmpty())
+        assertTrue(set.isNotEmpty())
+        assertTrue(set.any())
+        assertFalse(set.none())
+    }
+
+    @Test
+    fun count() {
+        val set = MutablePKeySet()
+        assertEquals(0, set.count())
+
+        set += 1KeySuffix
+        assertEquals(1, set.count())
+
+        set += 5KeySuffix
+        set += 6KeySuffix
+        set += 9KeySuffix
+        set += 11KeySuffix
+        set += 13KeySuffix
+
+        assertEquals(2, set.count { it < 6KeySuffix })
+        assertEquals(0, set.count { it < 0KeySuffix })
+    }
+
+    @Test
+    fun any() {
+        val set = MutablePKeySet()
+        set += 1KeySuffix
+        set += 5KeySuffix
+        set += 6KeySuffix
+        set += 9KeySuffix
+        set += 11KeySuffix
+        set += 13KeySuffix
+
+        assertTrue(set.any { it >= 11KeySuffix })
+        assertFalse(set.any { it < 0KeySuffix })
+    }
+
+    @Test
+    fun all() {
+        val set = MutablePKeySet()
+        set += 1KeySuffix
+        set += 5KeySuffix
+        set += 6KeySuffix
+        set += 9KeySuffix
+        set += 11KeySuffix
+        set += 13KeySuffix
+
+        assertTrue(set.all { it > 0KeySuffix })
+        assertFalse(set.all { it < 0KeySuffix })
+    }
+
+    @Test
+    fun trim() {
+        val set = mutablePKeySetOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix, 7KeySuffix)
+        val capacity = set.capacity
+        assertEquals(0, set.trim())
+        set.clear()
+        assertEquals(capacity, set.trim())
+        assertEquals(0, set.capacity)
+        set.addAll(pKeyArrayOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix, 7KeySuffix, 6KeySuffix, 8KeySuffix,
+            9KeySuffix, 10KeySuffix, 11KeySuffix, 12KeySuffix, 13KeySuffix, 14KeySuffix))
+        set.removeAll(pKeyArrayOf(6KeySuffix, 8KeySuffix, 9KeySuffix, 10KeySuffix, 11KeySuffix, 12KeySuffix, 13KeySuffix, 14KeySuffix))
+        assertTrue(set.trim() > 0)
+        assertEquals(capacity, set.capacity)
+    }
+
+    @Test
+    fun pKeySetOfEmpty() {
+        assertSame(emptyPKeySet(), pKeySetOf())
+        assertEquals(0, pKeySetOf().size)
+    }
+
+    @Test
+    fun pKeySetOfOne() {
+        val set = pKeySetOf(1KeySuffix)
+        assertEquals(1, set.size)
+        assertEquals(1KeySuffix, set.first())
+    }
+
+    @Test
+    fun pKeySetOfTwo() {
+        val set = pKeySetOf(1KeySuffix, 2KeySuffix)
+        assertEquals(2, set.size)
+        assertTrue(1KeySuffix in set)
+        assertTrue(2KeySuffix in set)
+        assertFalse(5KeySuffix in set)
+    }
+
+    @Test
+    fun pKeySetOfThree() {
+        val set = pKeySetOf(1KeySuffix, 2KeySuffix, 3KeySuffix)
+        assertEquals(3, set.size)
+        assertTrue(1KeySuffix in set)
+        assertTrue(2KeySuffix in set)
+        assertTrue(3KeySuffix in set)
+        assertFalse(5KeySuffix in set)
+    }
+
+    @Test
+    fun pKeySetOfFour() {
+        val set = pKeySetOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix)
+        assertEquals(4, set.size)
+        assertTrue(1KeySuffix in set)
+        assertTrue(2KeySuffix in set)
+        assertTrue(3KeySuffix in set)
+        assertTrue(4KeySuffix in set)
+        assertFalse(5KeySuffix in set)
+    }
+
+    @Test
+    fun mutablePKeySetOfOne() {
+        val set = mutablePKeySetOf(1KeySuffix)
+        assertEquals(1, set.size)
+        assertEquals(1KeySuffix, set.first())
+    }
+
+    @Test
+    fun mutablePKeySetOfTwo() {
+        val set = mutablePKeySetOf(1KeySuffix, 2KeySuffix)
+        assertEquals(2, set.size)
+        assertTrue(1KeySuffix in set)
+        assertTrue(2KeySuffix in set)
+        assertFalse(5KeySuffix in set)
+    }
+
+    @Test
+    fun mutablePKeySetOfThree() {
+        val set = mutablePKeySetOf(1KeySuffix, 2KeySuffix, 3KeySuffix)
+        assertEquals(3, set.size)
+        assertTrue(1KeySuffix in set)
+        assertTrue(2KeySuffix in set)
+        assertTrue(3KeySuffix in set)
+        assertFalse(5KeySuffix in set)
+    }
+
+    @Test
+    fun mutablePKeySetOfFour() {
+        val set = mutablePKeySetOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix)
+        assertEquals(4, set.size)
+        assertTrue(1KeySuffix in set)
+        assertTrue(2KeySuffix in set)
+        assertTrue(3KeySuffix in set)
+        assertTrue(4KeySuffix in set)
+        assertFalse(5KeySuffix in set)
+    }
+}
diff --git a/collection/collection/template/ValueClassList.kt.template b/collection/collection/template/ValueClassList.kt.template
new file mode 100644
index 0000000..34d2ea2
--- /dev/null
+++ b/collection/collection/template/ValueClassList.kt.template
@@ -0,0 +1,935 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:Suppress("NOTHING_TO_INLINE", "RedundantVisibilityModifier", "UnusedImport")
+/* ktlint-disable max-line-length */
+/* ktlint-disable import-ordering */
+
+package PACKAGE
+
+import androidx.collection.PRIMITIVEList
+import androidx.collection.MutablePRIMITIVEList
+import androidx.collection.emptyPRIMITIVEList
+import androidx.collection.mutablePRIMITIVEListOf
+import VALUE_PKG.VALUE_CLASS
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+import kotlin.jvm.JvmInline
+
+// To use this template, you must substitute several strings. You can copy this and search/replace
+// or use a sed command. These properties must be changed:
+// * PACKAGE - target package (e.g. androidx.compose.ui.ui.collection)
+// * VALUE_PKG - package in which the value class resides
+// * VALUE_CLASS - the value class contained in the list (e.g. Color or Offset)
+// * vALUE_CLASS - the value class, with the first letter lower case (e.g. color or offset)
+// * BACKING_PROPERTY - the field in VALUE_CLASS containing the backing primitive (e.g. packedValue)
+// * PRIMITIVE - the primitive type of the backing list (e.g. Long or Float)
+// * TO_PARAM - an operation done on the primitive to convert to the value class parameter
+//
+// For example, to create a ColorList:
+// sed -e "s/PACKAGE/androidx.compose.ui.graphics/" -e "s/VALUE_CLASS/Color/g" \
+//     -e "s/vALUE_CLASS/color/g" -e "s/BACKING_PROPERTY/value.toLong()/g" -e "s/PRIMITIVE/Long/g" \
+//     -e "s/TO_PARAM/.toULong()/g" -e "s/VALUE_PKG/androidx.compose.ui.graphics/g" \
+//     collection/collection/template/ValueClassList.kt.template \
+//     > compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/ColorList.kt
+
+/**
+ * [VALUE_CLASSList] is a [List]-like collection for [VALUE_CLASS] values. It allows retrieving
+ * the elements without boxing. [VALUE_CLASSList] is always backed by a [MutableVALUE_CLASSList],
+ * its [MutableList]-like subclass.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the list (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. It is also not safe to mutate during reentrancy --
+ * in the middle of a [forEach], for example. However, concurrent reads are safe.
+ */
+@OptIn(ExperimentalContracts::class)
+@JvmInline
+internal value class VALUE_CLASSList(val list: PRIMITIVEList) {
+    /**
+     * The number of elements in the [VALUE_CLASSList].
+     */
+    @get:androidx.annotation.IntRange(from = 0)
+    public inline val size: Int get() = list.size
+
+    /**
+     * Returns the last valid index in the [VALUE_CLASSList]. This can be `-1` when the list is empty.
+     */
+    @get:androidx.annotation.IntRange(from = -1)
+    public inline val lastIndex: Int get() = list.lastIndex
+
+    /**
+     * Returns an [IntRange] of the valid indices for this [VALUE_CLASSList].
+     */
+    public inline val indices: IntRange get() = list.indices
+
+    /**
+     * Returns `true` if the collection has no elements in it.
+     */
+    public inline fun none(): Boolean = list.none()
+
+    /**
+     * Returns `true` if there's at least one element in the collection.
+     */
+    public inline fun any(): Boolean = list.any()
+
+    /**
+     * Returns `true` if any of the elements give a `true` return value for [predicate].
+     */
+    public inline fun any(predicate: (element: VALUE_CLASS) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        return list.any { predicate(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns `true` if any of the elements give a `true` return value for [predicate] while
+     * iterating in the reverse order.
+     */
+    public inline fun reversedAny(predicate: (element: VALUE_CLASS) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        return list.reversedAny { predicate(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns `true` if the [VALUE_CLASSList] contains [element] or `false` otherwise.
+     */
+    public inline operator fun contains(element: VALUE_CLASS): Boolean =
+        list.contains(element.BACKING_PROPERTY)
+
+    /**
+     * Returns `true` if the [VALUE_CLASSList] contains all elements in [elements] or `false` if
+     * one or more are missing.
+     */
+    public inline fun containsAll(elements: VALUE_CLASSList): Boolean =
+        list.containsAll(elements.list)
+
+    /**
+     * Returns `true` if the [VALUE_CLASSList] contains all elements in [elements] or `false` if
+     * one or more are missing.
+     */
+    public inline fun containsAll(elements: MutableVALUE_CLASSList): Boolean =
+        list.containsAll(elements.list)
+
+    /**
+     * Returns the number of elements in this list.
+     */
+    public inline fun count(): Int = list.count()
+
+    /**
+     * Counts the number of elements matching [predicate].
+     * @return The number of elements in this list for which [predicate] returns true.
+     */
+    public inline fun count(predicate: (element: VALUE_CLASS) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        return list.count { predicate(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns the first element in the [VALUE_CLASSList] or throws a [NoSuchElementException] if
+     * it [isEmpty].
+     */
+    public inline fun first(): VALUE_CLASS = VALUE_CLASS(list.first()TO_PARAM)
+
+    /**
+     * Returns the first element in the [VALUE_CLASSList] for which [predicate] returns `true` or
+     * throws [NoSuchElementException] if nothing matches.
+     * @see indexOfFirst
+     */
+    public inline fun first(predicate: (element: VALUE_CLASS) -> Boolean): VALUE_CLASS {
+        contract { callsInPlace(predicate) }
+        return VALUE_CLASS(list.first { predicate(VALUE_CLASS(itTO_PARAM)) }TO_PARAM)
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [VALUE_CLASSList] in order.
+     * @param initial The value of `acc` for the first call to [operation] or return value if
+     * there are no elements in this list.
+     * @param operation function that takes current accumulator value and an element, and
+     * calculates the next accumulator value.
+     */
+    public inline fun <R> fold(initial: R, operation: (acc: R, element: VALUE_CLASS) -> R): R {
+        contract { callsInPlace(operation) }
+        return list.fold(initial) { acc, element ->
+            operation(acc, VALUE_CLASS(elementTO_PARAM))
+        }
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [VALUE_CLASSList] in order.
+     */
+    public inline fun <R> foldIndexed(
+        initial: R,
+        operation: (index: Int, acc: R, element: VALUE_CLASS) -> R
+    ): R {
+        contract { callsInPlace(operation) }
+        return list.foldIndexed(initial) { index, acc, element ->
+            operation(index, acc, VALUE_CLASS(elementTO_PARAM))
+        }
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [VALUE_CLASSList] in reverse order.
+     * @param initial The value of `acc` for the first call to [operation] or return value if
+     * there are no elements in this list.
+     * @param operation function that takes an element and the current accumulator value, and
+     * calculates the next accumulator value.
+     */
+    public inline fun <R> foldRight(initial: R, operation: (element: VALUE_CLASS, acc: R) -> R): R {
+        contract { callsInPlace(operation) }
+        return list.foldRight(initial) { element, acc ->
+            operation(VALUE_CLASS(elementTO_PARAM), acc)
+        }
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [VALUE_CLASSList] in reverse order.
+     */
+    public inline fun <R> foldRightIndexed(
+        initial: R,
+        operation: (index: Int, element: VALUE_CLASS, acc: R) -> R
+    ): R {
+        contract { callsInPlace(operation) }
+        return list.foldRightIndexed(initial) { index, element, acc ->
+            operation(index, VALUE_CLASS(elementTO_PARAM), acc)
+        }
+    }
+
+    /**
+     * Calls [block] for each element in the [VALUE_CLASSList], in order.
+     * @param block will be executed for every element in the list, accepting an element from
+     * the list
+     */
+    public inline fun forEach(block: (element: VALUE_CLASS) -> Unit) {
+        contract { callsInPlace(block) }
+        list.forEach { block(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Calls [block] for each element in the [VALUE_CLASSList] along with its index, in order.
+     * @param block will be executed for every element in the list, accepting the index and
+     * the element at that index.
+     */
+    public inline fun forEachIndexed(block: (index: Int, element: VALUE_CLASS) -> Unit) {
+        contract { callsInPlace(block) }
+        list.forEachIndexed { index, element ->
+            block(index, VALUE_CLASS(elementTO_PARAM))
+        }
+    }
+
+    /**
+     * Calls [block] for each element in the [VALUE_CLASSList] in reverse order.
+     * @param block will be executed for every element in the list, accepting an element from
+     * the list
+     */
+    public inline fun forEachReversed(block: (element: VALUE_CLASS) -> Unit) {
+        contract { callsInPlace(block) }
+        list.forEachReversed { block(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Calls [block] for each element in the [VALUE_CLASSList] along with its index, in reverse
+     * order.
+     * @param block will be executed for every element in the list, accepting the index and
+     * the element at that index.
+     */
+    public inline fun forEachReversedIndexed(block: (index: Int, element: VALUE_CLASS) -> Unit) {
+        contract { callsInPlace(block) }
+        list.forEachReversedIndexed { index, element ->
+            block(index, VALUE_CLASS(elementTO_PARAM))
+        }
+    }
+
+    /**
+     * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if
+     * the [index] is out of bounds of this collection.
+     */
+    public inline operator fun get(
+        @androidx.annotation.IntRange(from = 0) index: Int
+    ): VALUE_CLASS = VALUE_CLASS(list[index]TO_PARAM)
+
+    /**
+     * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if
+     * the [index] is out of bounds of this collection.
+     */
+    public inline fun elementAt(@androidx.annotation.IntRange(from = 0) index: Int): VALUE_CLASS =
+        VALUE_CLASS(list[index]TO_PARAM)
+
+    /**
+     * Returns the element at the given [index] or [defaultValue] if [index] is out of bounds
+     * of the collection.
+     * @param index The index of the element whose value should be returned
+     * @param defaultValue A lambda to call with [index] as a parameter to return a value at
+     * an index not in the list.
+     */
+    public inline fun elementAtOrElse(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        defaultValue: (index: Int) -> VALUE_CLASS
+    ): VALUE_CLASS =
+        VALUE_CLASS(list.elementAtOrElse(index) { defaultValue(it).BACKING_PROPERTY }TO_PARAM)
+
+    /**
+     * Returns the index of [element] in the [VALUE_CLASSList] or `-1` if [element] is not there.
+     */
+    public inline fun indexOf(element: VALUE_CLASS): Int =
+        list.indexOf(element.BACKING_PROPERTY)
+
+    /**
+     * Returns the index if the first element in the [VALUE_CLASSList] for which [predicate]
+     * returns `true`.
+     */
+    public inline fun indexOfFirst(predicate: (element: VALUE_CLASS) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        return list.indexOfFirst { predicate(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns the index if the last element in the [VALUE_CLASSList] for which [predicate]
+     * returns `true`.
+     */
+    public inline fun indexOfLast(predicate: (element: VALUE_CLASS) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        return list.indexOfLast { predicate(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns `true` if the [VALUE_CLASSList] has no elements in it or `false` otherwise.
+     */
+    public inline fun isEmpty(): Boolean = list.isEmpty()
+
+    /**
+     * Returns `true` if there are elements in the [VALUE_CLASSList] or `false` if it is empty.
+     */
+    public inline fun isNotEmpty(): Boolean = list.isNotEmpty()
+
+    /**
+     * Returns the last element in the [VALUE_CLASSList] or throws a [NoSuchElementException] if
+     * it [isEmpty].
+     */
+    public inline fun last(): VALUE_CLASS = VALUE_CLASS(list.last()TO_PARAM)
+
+    /**
+     * Returns the last element in the [VALUE_CLASSList] for which [predicate] returns `true` or
+     * throws [NoSuchElementException] if nothing matches.
+     * @see indexOfLast
+     */
+    public inline fun last(predicate: (element: VALUE_CLASS) -> Boolean): VALUE_CLASS {
+        contract { callsInPlace(predicate) }
+        return VALUE_CLASS(list.last { predicate(VALUE_CLASS(itTO_PARAM)) }TO_PARAM)
+    }
+
+    /**
+     * Returns the index of the last element in the [VALUE_CLASSList] that is the same as
+     * [element] or `-1` if no elements match.
+     */
+    public inline fun lastIndexOf(element: VALUE_CLASS): Int =
+        list.lastIndexOf(element.BACKING_PROPERTY)
+
+    /**
+     * Returns a String representation of the list, surrounded by "[]" and each element
+     * separated by ", ".
+     */
+    override fun toString(): String {
+        if (isEmpty()) {
+            return "[]"
+        }
+        return buildString {
+            append('[')
+            forEachIndexed { index: Int, element: VALUE_CLASS ->
+                if (index != 0) {
+                    append(',').append(' ')
+                }
+                append(element)
+            }
+            append(']')
+        }
+    }
+}
+
+/**
+ * [MutableVALUE_CLASSList] is a [MutableList]-like collection for [VALUE_CLASS] values.
+ * It allows storing and retrieving the elements without boxing. Immutable
+ * access is available through its base class [VALUE_CLASSList], which has a [List]-like
+ * interface.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the list (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. It is also not safe to mutate during reentrancy --
+ * in the middle of a [forEach], for example. However, concurrent reads are safe.
+ *
+ * @constructor Creates a [MutableVALUE_CLASSList] with a [capacity] of `initialCapacity`.
+ */
+@OptIn(ExperimentalContracts::class)
+@JvmInline
+internal value class MutableVALUE_CLASSList(val list: MutablePRIMITIVEList) {
+    public constructor(initialCapacity: Int = 16) : this(MutablePRIMITIVEList(initialCapacity))
+
+    /**
+     * The number of elements in the [VALUE_CLASSList].
+     */
+    @get:androidx.annotation.IntRange(from = 0)
+    public inline val size: Int get() = list.size
+
+    /**
+     * Returns the last valid index in the [VALUE_CLASSList]. This can be `-1` when the list is empty.
+     */
+    @get:androidx.annotation.IntRange(from = -1)
+    public inline val lastIndex: Int get() = list.lastIndex
+
+    /**
+     * Returns an [IntRange] of the valid indices for this [VALUE_CLASSList].
+     */
+    public inline val indices: IntRange get() = list.indices
+
+    /**
+     * Returns `true` if the collection has no elements in it.
+     */
+    public inline fun none(): Boolean = list.none()
+
+    /**
+     * Returns `true` if there's at least one element in the collection.
+     */
+    public inline fun any(): Boolean = list.any()
+
+    /**
+     * Returns `true` if any of the elements give a `true` return value for [predicate].
+     */
+    public inline fun any(predicate: (element: VALUE_CLASS) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        return list.any { predicate(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns `true` if any of the elements give a `true` return value for [predicate] while
+     * iterating in the reverse order.
+     */
+    public inline fun reversedAny(predicate: (element: VALUE_CLASS) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        return list.reversedAny { predicate(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns `true` if the [VALUE_CLASSList] contains [element] or `false` otherwise.
+     */
+    public inline operator fun contains(element: VALUE_CLASS): Boolean =
+        list.contains(element.BACKING_PROPERTY)
+
+    /**
+     * Returns `true` if the [VALUE_CLASSList] contains all elements in [elements] or `false` if
+     * one or more are missing.
+     */
+    public inline fun containsAll(elements: VALUE_CLASSList): Boolean =
+        list.containsAll(elements.list)
+
+    /**
+     * Returns `true` if the [VALUE_CLASSList] contains all elements in [elements] or `false` if
+     * one or more are missing.
+     */
+    public inline fun containsAll(elements: MutableVALUE_CLASSList): Boolean =
+        list.containsAll(elements.list)
+
+    /**
+     * Returns the number of elements in this list.
+     */
+    public inline fun count(): Int = list.count()
+
+    /**
+     * Counts the number of elements matching [predicate].
+     * @return The number of elements in this list for which [predicate] returns true.
+     */
+    public inline fun count(predicate: (element: VALUE_CLASS) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        return list.count { predicate(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns the first element in the [VALUE_CLASSList] or throws a [NoSuchElementException] if
+     * it [isEmpty].
+     */
+    public inline fun first(): VALUE_CLASS = VALUE_CLASS(list.first()TO_PARAM)
+
+    /**
+     * Returns the first element in the [VALUE_CLASSList] for which [predicate] returns `true` or
+     * throws [NoSuchElementException] if nothing matches.
+     * @see indexOfFirst
+     */
+    public inline fun first(predicate: (element: VALUE_CLASS) -> Boolean): VALUE_CLASS {
+        contract { callsInPlace(predicate) }
+        return VALUE_CLASS(list.first { predicate(VALUE_CLASS(itTO_PARAM)) }TO_PARAM)
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [VALUE_CLASSList] in order.
+     * @param initial The value of `acc` for the first call to [operation] or return value if
+     * there are no elements in this list.
+     * @param operation function that takes current accumulator value and an element, and
+     * calculates the next accumulator value.
+     */
+    public inline fun <R> fold(initial: R, operation: (acc: R, element: VALUE_CLASS) -> R): R {
+        contract { callsInPlace(operation) }
+        return list.fold(initial) { acc, element ->
+            operation(acc, VALUE_CLASS(elementTO_PARAM))
+        }
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [VALUE_CLASSList] in order.
+     */
+    public inline fun <R> foldIndexed(
+        initial: R,
+        operation: (index: Int, acc: R, element: VALUE_CLASS) -> R
+    ): R {
+        contract { callsInPlace(operation) }
+        return list.foldIndexed(initial) { index, acc, element ->
+            operation(index, acc, VALUE_CLASS(elementTO_PARAM))
+        }
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [VALUE_CLASSList] in reverse order.
+     * @param initial The value of `acc` for the first call to [operation] or return value if
+     * there are no elements in this list.
+     * @param operation function that takes an element and the current accumulator value, and
+     * calculates the next accumulator value.
+     */
+    public inline fun <R> foldRight(initial: R, operation: (element: VALUE_CLASS, acc: R) -> R): R {
+        contract { callsInPlace(operation) }
+        return list.foldRight(initial) { element, acc ->
+            operation(VALUE_CLASS(elementTO_PARAM), acc)
+        }
+    }
+
+    /**
+     * Accumulates values, starting with [initial], and applying [operation] to each element
+     * in the [VALUE_CLASSList] in reverse order.
+     */
+    public inline fun <R> foldRightIndexed(
+        initial: R,
+        operation: (index: Int, element: VALUE_CLASS, acc: R) -> R
+    ): R {
+        contract { callsInPlace(operation) }
+        return list.foldRightIndexed(initial) { index, element, acc ->
+            operation(index, VALUE_CLASS(elementTO_PARAM), acc)
+        }
+    }
+
+    /**
+     * Calls [block] for each element in the [VALUE_CLASSList], in order.
+     * @param block will be executed for every element in the list, accepting an element from
+     * the list
+     */
+    public inline fun forEach(block: (element: VALUE_CLASS) -> Unit) {
+        contract { callsInPlace(block) }
+        list.forEach { block(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Calls [block] for each element in the [VALUE_CLASSList] along with its index, in order.
+     * @param block will be executed for every element in the list, accepting the index and
+     * the element at that index.
+     */
+    public inline fun forEachIndexed(block: (index: Int, element: VALUE_CLASS) -> Unit) {
+        contract { callsInPlace(block) }
+        list.forEachIndexed { index, element ->
+            block(index, VALUE_CLASS(elementTO_PARAM))
+        }
+    }
+
+    /**
+     * Calls [block] for each element in the [VALUE_CLASSList] in reverse order.
+     * @param block will be executed for every element in the list, accepting an element from
+     * the list
+     */
+    public inline fun forEachReversed(block: (element: VALUE_CLASS) -> Unit) {
+        contract { callsInPlace(block) }
+        list.forEachReversed { block(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Calls [block] for each element in the [VALUE_CLASSList] along with its index, in reverse
+     * order.
+     * @param block will be executed for every element in the list, accepting the index and
+     * the element at that index.
+     */
+    public inline fun forEachReversedIndexed(block: (index: Int, element: VALUE_CLASS) -> Unit) {
+        contract { callsInPlace(block) }
+        list.forEachReversedIndexed { index, element ->
+            block(index, VALUE_CLASS(elementTO_PARAM))
+        }
+    }
+
+    /**
+     * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if
+     * the [index] is out of bounds of this collection.
+     */
+    public inline operator fun get(
+        @androidx.annotation.IntRange(from = 0) index: Int
+    ): VALUE_CLASS = VALUE_CLASS(list[index]TO_PARAM)
+
+    /**
+     * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if
+     * the [index] is out of bounds of this collection.
+     */
+    public inline fun elementAt(@androidx.annotation.IntRange(from = 0) index: Int): VALUE_CLASS =
+        VALUE_CLASS(list[index]TO_PARAM)
+
+    /**
+     * Returns the element at the given [index] or [defaultValue] if [index] is out of bounds
+     * of the collection.
+     * @param index The index of the element whose value should be returned
+     * @param defaultValue A lambda to call with [index] as a parameter to return a value at
+     * an index not in the list.
+     */
+    public inline fun elementAtOrElse(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        defaultValue: (index: Int) -> VALUE_CLASS
+    ): VALUE_CLASS =
+        VALUE_CLASS(list.elementAtOrElse(index) { defaultValue(it).BACKING_PROPERTY }TO_PARAM)
+
+    /**
+     * Returns the index of [element] in the [VALUE_CLASSList] or `-1` if [element] is not there.
+     */
+    public inline fun indexOf(element: VALUE_CLASS): Int =
+        list.indexOf(element.BACKING_PROPERTY)
+
+    /**
+     * Returns the index if the first element in the [VALUE_CLASSList] for which [predicate]
+     * returns `true`.
+     */
+    public inline fun indexOfFirst(predicate: (element: VALUE_CLASS) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        return list.indexOfFirst { predicate(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns the index if the last element in the [VALUE_CLASSList] for which [predicate]
+     * returns `true`.
+     */
+    public inline fun indexOfLast(predicate: (element: VALUE_CLASS) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        return list.indexOfLast { predicate(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns `true` if the [VALUE_CLASSList] has no elements in it or `false` otherwise.
+     */
+    public inline fun isEmpty(): Boolean = list.isEmpty()
+
+    /**
+     * Returns `true` if there are elements in the [VALUE_CLASSList] or `false` if it is empty.
+     */
+    public inline fun isNotEmpty(): Boolean = list.isNotEmpty()
+
+    /**
+     * Returns the last element in the [VALUE_CLASSList] or throws a [NoSuchElementException] if
+     * it [isEmpty].
+     */
+    public inline fun last(): VALUE_CLASS = VALUE_CLASS(list.last()TO_PARAM)
+
+    /**
+     * Returns the last element in the [VALUE_CLASSList] for which [predicate] returns `true` or
+     * throws [NoSuchElementException] if nothing matches.
+     * @see indexOfLast
+     */
+    public inline fun last(predicate: (element: VALUE_CLASS) -> Boolean): VALUE_CLASS {
+        contract { callsInPlace(predicate) }
+        return VALUE_CLASS(list.last { predicate(VALUE_CLASS(itTO_PARAM)) }TO_PARAM)
+    }
+
+    /**
+     * Returns the index of the last element in the [VALUE_CLASSList] that is the same as
+     * [element] or `-1` if no elements match.
+     */
+    public inline fun lastIndexOf(element: VALUE_CLASS): Int =
+        list.lastIndexOf(element.BACKING_PROPERTY)
+
+    /**
+     * Returns a String representation of the list, surrounded by "[]" and each element
+     * separated by ", ".
+     */
+    override fun toString(): String = asVALUE_CLASSList().toString()
+
+    /**
+     * Returns a read-only interface to the list.
+     */
+    public inline fun asVALUE_CLASSList(): VALUE_CLASSList = VALUE_CLASSList(list)
+
+    /**
+     * Returns the total number of elements that can be held before the [MutableVALUE_CLASSList] must
+     * grow.
+     *
+     * @see ensureCapacity
+     */
+    public inline val capacity: Int
+        get() = list.capacity
+
+    /**
+     * Adds [element] to the [MutableVALUE_CLASSList] and returns `true`.
+     */
+    public inline fun add(element: VALUE_CLASS): Boolean =
+        list.add(element.BACKING_PROPERTY)
+
+    /**
+     * Adds [element] to the [MutableVALUE_CLASSList] at the given [index], shifting over any
+     * elements at [index] and after, if any.
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
+     */
+    public inline fun add(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        element: VALUE_CLASS
+    ) = list.add(index, element.BACKING_PROPERTY)
+
+    /**
+     * Adds all [elements] to the [MutableVALUE_CLASSList] at the given [index], shifting over any
+     * elements at [index] and after, if any.
+     * @return `true` if the [MutableVALUE_CLASSList] was changed or `false` if [elements] was empty
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
+     */
+    public inline fun addAll(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        elements: VALUE_CLASSList
+    ): Boolean = list.addAll(index, elements.list)
+
+    /**
+     * Adds all [elements] to the [MutableVALUE_CLASSList] at the given [index], shifting over any
+     * elements at [index] and after, if any.
+     * @return `true` if the [MutableVALUE_CLASSList] was changed or `false` if [elements] was empty
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
+     */
+    public inline fun addAll(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        elements: MutableVALUE_CLASSList
+    ): Boolean = list.addAll(index, elements.list)
+
+    /**
+     * Adds all [elements] to the end of the [MutableVALUE_CLASSList] and returns `true` if the
+     * [MutableVALUE_CLASSList] was changed or `false` if [elements] was empty.
+     */
+    public inline fun addAll(elements: VALUE_CLASSList): Boolean = list.addAll(elements.list)
+
+    /**
+     * Adds all [elements] to the end of the [MutableVALUE_CLASSList].
+     */
+    public inline operator fun plusAssign(elements: VALUE_CLASSList) =
+        list.plusAssign(elements.list)
+
+    /**
+     * Adds all [elements] to the end of the [MutableVALUE_CLASSList] and returns `true` if the
+     * [MutableVALUE_CLASSList] was changed or `false` if [elements] was empty.
+     */
+    public inline fun addAll(elements: MutableVALUE_CLASSList): Boolean = list.addAll(elements.list)
+
+    /**
+     * Adds all [elements] to the end of the [MutableVALUE_CLASSList].
+     */
+    public inline operator fun plusAssign(elements: MutableVALUE_CLASSList) =
+        list.plusAssign(elements.list)
+
+    /**
+     * Removes all elements in the [MutableVALUE_CLASSList]. The storage isn't released.
+     * @see trim
+     */
+    public inline fun clear() = list.clear()
+
+    /**
+     * Reduces the internal storage. If [capacity] is greater than [minCapacity] and [size], the
+     * internal storage is reduced to the maximum of [size] and [minCapacity].
+     * @see ensureCapacity
+     */
+    public inline fun trim(minCapacity: Int = size) = list.trim(minCapacity)
+
+    /**
+     * Ensures that there is enough space to store [capacity] elements in the [MutableVALUE_CLASSList].
+     * @see trim
+     */
+    public inline fun ensureCapacity(capacity: Int) = list.ensureCapacity(capacity)
+
+    /**
+     * [add] [element] to the [MutableVALUE_CLASSList].
+     */
+    public inline operator fun plusAssign(element: VALUE_CLASS) =
+        list.plusAssign(element.BACKING_PROPERTY)
+
+    /**
+     * [remove] [element] from the [MutableVALUE_CLASSList]
+     */
+    public inline operator fun minusAssign(element: VALUE_CLASS) =
+        list.minusAssign(element.BACKING_PROPERTY)
+
+    /**
+     * Removes [element] from the [MutableVALUE_CLASSList]. If [element] was in the [MutableVALUE_CLASSList]
+     * and was removed, `true` will be returned, or `false` will be returned if the element
+     * was not found.
+     */
+    public inline fun remove(element: VALUE_CLASS): Boolean =
+        list.remove(element.BACKING_PROPERTY)
+
+    /**
+     * Removes all [elements] from the [MutableVALUE_CLASSList] and returns `true` if anything was removed.
+     */
+    public inline fun removeAll(elements: VALUE_CLASSList): Boolean =
+        list.removeAll(elements.list)
+
+    /**
+     * Removes all [elements] from the [MutableVALUE_CLASSList].
+     */
+    public inline operator fun minusAssign(elements: VALUE_CLASSList) =
+        list.minusAssign(elements.list)
+
+    /**
+     * Removes all [elements] from the [MutableVALUE_CLASSList] and returns `true` if anything was removed.
+     */
+    public inline fun removeAll(elements: MutableVALUE_CLASSList): Boolean =
+        list.removeAll(elements.list)
+
+    /**
+     * Removes all [elements] from the [MutableVALUE_CLASSList].
+     */
+    public inline operator fun minusAssign(elements: MutableVALUE_CLASSList) =
+        list.minusAssign(elements.list)
+
+    /**
+     * Removes the element at the given [index] and returns it.
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [lastIndex], inclusive
+     */
+    public inline fun removeAt(@androidx.annotation.IntRange(from = 0) index: Int): VALUE_CLASS =
+        VALUE_CLASS(list.removeAt(index)TO_PARAM)
+
+    /**
+     * Removes items from index [start] (inclusive) to [end] (exclusive).
+     * @throws IndexOutOfBoundsException if [start] or [end] isn't between 0 and [size], inclusive
+     * @throws IllegalArgumentException if [start] is greater than [end]
+     */
+    public inline fun removeRange(
+        @androidx.annotation.IntRange(from = 0) start: Int,
+        @androidx.annotation.IntRange(from = 0) end: Int
+    ) = list.removeRange(start, end)
+
+    /**
+     * Keeps only [elements] in the [MutableVALUE_CLASSList] and removes all other values.
+     * @return `true` if the [MutableVALUE_CLASSList] has changed.
+     */
+    public inline fun retainAll(elements: VALUE_CLASSList): Boolean =
+        list.retainAll(elements.list)
+
+    /**
+     * Keeps only [elements] in the [MutableVALUE_CLASSList] and removes all other values.
+     * @return `true` if the [MutableVALUE_CLASSList] has changed.
+     */
+    public inline fun retainAll(elements: MutableVALUE_CLASSList): Boolean =
+        list.retainAll(elements.list)
+
+    /**
+     * Sets the value at [index] to [element].
+     * @return the previous value set at [index]
+     * @throws IndexOutOfBoundsException if [index] isn't between 0 and [lastIndex], inclusive
+     */
+    public inline operator fun set(
+        @androidx.annotation.IntRange(from = 0) index: Int,
+        element: VALUE_CLASS
+    ): VALUE_CLASS = VALUE_CLASS(list.set(index, element.BACKING_PROPERTY)TO_PARAM)
+}
+
+/**
+ * @return a read-only [VALUE_CLASSList] with nothing in it.
+ */
+internal inline fun emptyVALUE_CLASSList(): VALUE_CLASSList = VALUE_CLASSList(emptyPRIMITIVEList())
+
+/**
+ * @return a read-only [VALUE_CLASSList] with nothing in it.
+ */
+internal inline fun vALUE_CLASSListOf(): VALUE_CLASSList = VALUE_CLASSList(emptyPRIMITIVEList())
+
+/**
+ * @return a new read-only [VALUE_CLASSList] with [element1] as the only item in the list.
+ */
+internal inline fun vALUE_CLASSListOf(element1: VALUE_CLASS): VALUE_CLASSList =
+    VALUE_CLASSList(mutablePRIMITIVEListOf(element1.BACKING_PROPERTY))
+
+/**
+ * @return a new read-only [VALUE_CLASSList] with 2 elements, [element1] and [element2], in order.
+ */
+internal inline fun vALUE_CLASSListOf(element1: VALUE_CLASS, element2: VALUE_CLASS): VALUE_CLASSList =
+    VALUE_CLASSList(
+        mutablePRIMITIVEListOf(
+            element1.BACKING_PROPERTY,
+            element2.BACKING_PROPERTY
+        )
+    )
+
+/**
+ * @return a new read-only [VALUE_CLASSList] with 3 elements, [element1], [element2], and [element3],
+ * in order.
+ */
+internal inline fun vALUE_CLASSListOf(
+        element1: VALUE_CLASS,
+        element2: VALUE_CLASS,
+        element3: VALUE_CLASS
+): VALUE_CLASSList = VALUE_CLASSList(
+    mutablePRIMITIVEListOf(
+        element1.BACKING_PROPERTY,
+        element2.BACKING_PROPERTY,
+        element3.BACKING_PROPERTY
+    )
+)
+
+/**
+ * @return a new empty [MutableVALUE_CLASSList] with the default capacity.
+ */
+internal inline fun mutableVALUE_CLASSListOf(): MutableVALUE_CLASSList =
+    MutableVALUE_CLASSList(MutablePRIMITIVEList())
+
+/**
+ * @return a new [MutableVALUE_CLASSList] with [element1] as the only item in the list.
+ */
+internal inline fun mutableVALUE_CLASSListOf(element1: VALUE_CLASS): MutableVALUE_CLASSList =
+    MutableVALUE_CLASSList(mutablePRIMITIVEListOf(element1.BACKING_PROPERTY))
+
+/**
+ * @return a new [MutableVALUE_CLASSList] with 2 elements, [element1] and [element2], in order.
+ */
+internal inline fun mutableVALUE_CLASSListOf(
+        element1: VALUE_CLASS,
+        element2: VALUE_CLASS
+    ): MutableVALUE_CLASSList = MutableVALUE_CLASSList(
+        mutablePRIMITIVEListOf(
+            element1.BACKING_PROPERTY,
+            element2.BACKING_PROPERTY
+        )
+    )
+
+/**
+ * @return a new [MutableVALUE_CLASSList] with 3 elements, [element1], [element2], and [element3],
+ * in order.
+ */
+internal inline fun mutableVALUE_CLASSListOf(
+        element1: VALUE_CLASS,
+        element2: VALUE_CLASS,
+        element3: VALUE_CLASS
+): MutableVALUE_CLASSList = MutableVALUE_CLASSList(
+    mutablePRIMITIVEListOf(
+        element1.BACKING_PROPERTY,
+        element2.BACKING_PROPERTY,
+        element3.BACKING_PROPERTY
+    )
+)
diff --git a/collection/collection/template/ValueClassSet.kt.template b/collection/collection/template/ValueClassSet.kt.template
new file mode 100644
index 0000000..33d7af4
--- /dev/null
+++ b/collection/collection/template/ValueClassSet.kt.template
@@ -0,0 +1,554 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress(
+    "RedundantVisibilityModifier",
+    "KotlinRedundantDiagnosticSuppress",
+    "KotlinConstantConditions",
+    "PropertyName",
+    "ConstPropertyName",
+    "PrivatePropertyName",
+    "NOTHING_TO_INLINE",
+    "UnusedImport"
+)
+
+package PACKAGE
+
+/* ktlint-disable max-line-length */
+/* ktlint-disable import-ordering */
+
+import androidx.collection.PRIMITIVESet
+import androidx.collection.MutablePRIMITIVESet
+import androidx.collection.emptyPRIMITIVESet
+import androidx.collection.mutablePRIMITIVESetOf
+import VALUE_PKG.VALUE_CLASS
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+import kotlin.jvm.JvmInline
+
+/* ktlint-disable max-line-length */
+// To use this template, you must substitute several strings. You can copy this and search/replace
+// or use a sed command. These properties must be changed:
+// * PACKAGE - target package (e.g. androidx.compose.ui.ui.collection)
+// * VALUE_PKG - package in which the value class resides
+// * VALUE_CLASS - the value class contained in the set (e.g. Color or Offset)
+// * vALUE_CLASS - the value class, with the first letter lower case (e.g. color or offset)
+// * BACKING_PROPERTY - the field in VALUE_CLASS containing the backing primitive (e.g. packedValue)
+// * PRIMITIVE - the primitive type of the backing set (e.g. Long or Float)
+// * TO_PARAM - an operation done on the primitive to convert to the value class parameter
+//
+// For example, to create a ColorSet:
+// sed -e "s/PACKAGE/androidx.compose.ui.graphics/" -e "s/VALUE_CLASS/Color/g" \
+//     -e "s/vALUE_CLASS/color/g" -e "s/BACKING_PROPERTY/value.toLong()/g" -e "s/PRIMITIVE/Long/g" \
+//     -e "s/TO_PARAM/.toULong()/g" -e "s/VALUE_PKG/androidx.collection/g" \
+//     collection/collection/template/ValueClassSet.kt.template \
+//     > compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/ColorSet.kt
+
+/**
+ * Returns an empty, read-only [VALUE_CLASSSet].
+ */
+internal inline fun emptyVALUE_CLASSSet(): VALUE_CLASSSet = VALUE_CLASSSet(emptyPRIMITIVESet())
+
+/**
+ * Returns an empty, read-only [VALUE_CLASSSet].
+ */
+internal inline fun vALUE_CLASSSetOf(): VALUE_CLASSSet = VALUE_CLASSSet(emptyPRIMITIVESet())
+
+/**
+ * Returns a new read-only [VALUE_CLASSSet] with only [element1] in it.
+ */
+internal inline fun vALUE_CLASSSetOf(element1: VALUE_CLASS): VALUE_CLASSSet =
+    VALUE_CLASSSet(mutablePRIMITIVESetOf(element1.BACKING_PROPERTY))
+
+/**
+ * Returns a new read-only [VALUE_CLASSSet] with only [element1] and [element2] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+internal fun vALUE_CLASSSetOf(
+    element1: VALUE_CLASS,
+    element2: VALUE_CLASS
+): VALUE_CLASSSet =
+    VALUE_CLASSSet(
+        mutablePRIMITIVESetOf(
+            element1.BACKING_PROPERTY,
+            element2.BACKING_PROPERTY,
+        )
+    )
+
+/**
+ * Returns a new read-only [VALUE_CLASSSet] with only [element1], [element2], and [element3] in it.
+ */
+@Suppress("UNCHECKED_CAST")
+internal fun vALUE_CLASSSetOf(
+    element1: VALUE_CLASS,
+    element2: VALUE_CLASS,
+    element3: VALUE_CLASS
+): VALUE_CLASSSet = VALUE_CLASSSet(
+    mutablePRIMITIVESetOf(
+        element1.BACKING_PROPERTY,
+        element2.BACKING_PROPERTY,
+        element3.BACKING_PROPERTY,
+    )
+)
+
+/**
+ * Returns a new [MutableVALUE_CLASSSet].
+ */
+internal fun mutableVALUE_CLASSSetOf(): MutableVALUE_CLASSSet = MutableVALUE_CLASSSet(
+    MutablePRIMITIVESet()
+)
+
+/**
+ * Returns a new [MutableVALUE_CLASSSet] with only [element1] in it.
+ */
+internal fun mutableVALUE_CLASSSetOf(element1: VALUE_CLASS): MutableVALUE_CLASSSet =
+    MutableVALUE_CLASSSet(mutablePRIMITIVESetOf(element1.BACKING_PROPERTY))
+
+/**
+ * Returns a new [MutableVALUE_CLASSSet] with only [element1] and [element2] in it.
+ */
+internal fun mutableVALUE_CLASSSetOf(
+    element1: VALUE_CLASS,
+    element2: VALUE_CLASS
+): MutableVALUE_CLASSSet =
+    MutableVALUE_CLASSSet(
+        mutablePRIMITIVESetOf(
+            element1.BACKING_PROPERTY,
+            element2.BACKING_PROPERTY,
+        )
+    )
+
+/**
+ * Returns a new [MutableVALUE_CLASSSet] with only [element1], [element2], and [element3] in it.
+ */
+internal fun mutableVALUE_CLASSSetOf(
+    element1: VALUE_CLASS,
+    element2: VALUE_CLASS,
+    element3: VALUE_CLASS
+): MutableVALUE_CLASSSet =
+    MutableVALUE_CLASSSet(
+        mutablePRIMITIVESetOf(
+            element1.BACKING_PROPERTY,
+            element2.BACKING_PROPERTY,
+            element3.BACKING_PROPERTY,
+        )
+    )
+
+/**
+ * [VALUE_CLASSSet] is a container with a [Set]-like interface designed to avoid
+ * allocations, including boxing.
+ *
+ * This implementation makes no guarantee as to the order of the elements,
+ * nor does it make guarantees that the order remains constant over time.
+ *
+ * Though [VALUE_CLASSSet] offers a read-only interface, it is always backed
+ * by a [MutableVALUE_CLASSSet]. Read operations alone are thread-safe. However,
+ * any mutations done through the backing [MutableVALUE_CLASSSet] while reading
+ * on another thread are not safe and the developer must protect the set
+ * from such changes during read operations.
+ *
+ * @see [MutableVALUE_CLASSSet]
+ */
+@OptIn(ExperimentalContracts::class)
+@JvmInline
+internal value class VALUE_CLASSSet(val set: PRIMITIVESet) {
+    /**
+     * Returns the number of elements that can be stored in this set
+     * without requiring internal storage reallocation.
+     */
+    @get:androidx.annotation.IntRange(from = 0)
+    public inline val capacity: Int
+        get() = set.capacity
+
+    /**
+     * Returns the number of elements in this set.
+     */
+    @get:androidx.annotation.IntRange(from = 0)
+    public inline val size: Int
+        get() = set.size
+
+    /**
+     * Returns `true` if this set has at least one element.
+     */
+    public inline fun any(): Boolean = set.any()
+
+    /**
+     * Returns `true` if this set has no elements.
+     */
+    public inline fun none(): Boolean = set.none()
+
+    /**
+     * Indicates whether this set is empty.
+     */
+    public inline fun isEmpty(): Boolean = set.isEmpty()
+
+    /**
+     * Returns `true` if this set is not empty.
+     */
+    public inline fun isNotEmpty(): Boolean = set.isNotEmpty()
+
+    /**
+     * Returns the first element in the collection.
+     * @throws NoSuchElementException if the collection is empty
+     */
+    public inline fun first(): VALUE_CLASS = VALUE_CLASS(set.first()TO_PARAM)
+
+    /**
+     * Returns the first element in the collection for which [predicate] returns `true`.
+     *
+     * **Note** There is no mechanism for both determining if there is an element that matches
+     * [predicate] _and_ returning it if it exists. Developers should use [forEach] to achieve
+     * this behavior.
+     *
+     * @param predicate Called on elements of the set, returning `true` for an element that matches
+     * or `false` if it doesn't
+     * @return An element in the set for which [predicate] returns `true`.
+     * @throws NoSuchElementException if [predicate] returns `false` for all elements or the
+     * collection is empty.
+     */
+    public inline fun first(predicate: (element: VALUE_CLASS) -> Boolean): VALUE_CLASS =
+        VALUE_CLASS(set.first { predicate(VALUE_CLASS(itTO_PARAM)) }TO_PARAM)
+
+    /**
+     * Iterates over every element stored in this set by invoking
+     * the specified [block] lambda.
+     * @param block called with each element in the set
+     */
+    public inline fun forEach(block: (element: VALUE_CLASS) -> Unit) {
+        contract { callsInPlace(block) }
+        set.forEach { block(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns true if all elements match the given [predicate].
+     * @param predicate called for elements in the set to determine if it returns return `true` for
+     * all elements.
+     */
+    public inline fun all(predicate: (element: VALUE_CLASS) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        return set.all { predicate(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns true if at least one element matches the given [predicate].
+     * @param predicate called for elements in the set to determine if it returns `true` for any
+     * elements.
+     */
+    public inline fun any(predicate: (element: VALUE_CLASS) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        return set.any { predicate(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns the number of elements in this set.
+     */
+    @androidx.annotation.IntRange(from = 0)
+    public inline fun count(): Int = set.count()
+
+    /**
+     * Returns the number of elements matching the given [predicate].
+     * @param predicate Called for all elements in the set to count the number for which it returns
+     * `true`.
+     */
+    @androidx.annotation.IntRange(from = 0)
+    public inline fun count(predicate: (element: VALUE_CLASS) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        return set.count { predicate(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns `true` if the specified [element] is present in this set, `false`
+     * otherwise.
+     * @param element The element to look for in this set
+     */
+    public inline operator fun contains(element: VALUE_CLASS): Boolean =
+        set.contains(element.BACKING_PROPERTY)
+
+    /**
+     * Returns a string representation of this set. The set is denoted in the
+     * string by the `{}`. Each element is separated by `, `.
+     */
+    public override fun toString(): String {
+        if (isEmpty()) {
+            return "[]"
+        }
+
+        val s = StringBuilder().append('[')
+        var index = 0
+        forEach { element ->
+            if (index++ != 0) {
+                s.append(',').append(' ')
+            }
+            s.append(element)
+        }
+        return s.append(']').toString()
+    }
+}
+
+/**
+ * [MutableVALUE_CLASSSet] is a container with a [MutableSet]-like interface based on a flat
+ * hash table implementation. The underlying implementation is designed to avoid
+ * all allocations on insertion, removal, retrieval, and iteration. Allocations
+ * may still happen on insertion when the underlying storage needs to grow to
+ * accommodate newly added elements to the set.
+ *
+ * This implementation makes no guarantee as to the order of the elements stored,
+ * nor does it make guarantees that the order remains constant over time.
+ *
+ * This implementation is not thread-safe: if multiple threads access this
+ * container concurrently, and one or more threads modify the structure of
+ * the set (insertion or removal for instance), the calling code must provide
+ * the appropriate synchronization. Concurrent reads are however safe.
+ */
+@OptIn(ExperimentalContracts::class)
+@JvmInline
+internal value class MutableVALUE_CLASSSet(val set: MutablePRIMITIVESet) {
+    /**
+     * Returns the number of elements that can be stored in this set
+     * without requiring internal storage reallocation.
+     */
+    @get:androidx.annotation.IntRange(from = 0)
+    public inline val capacity: Int
+        get() = set.capacity
+
+    /**
+     * Returns the number of elements in this set.
+     */
+    @get:androidx.annotation.IntRange(from = 0)
+    public inline val size: Int
+        get() = set.size
+
+    /**
+     * Returns `true` if this set has at least one element.
+     */
+    public inline fun any(): Boolean = set.any()
+
+    /**
+     * Returns `true` if this set has no elements.
+     */
+    public inline fun none(): Boolean = set.none()
+
+    /**
+     * Indicates whether this set is empty.
+     */
+    public inline fun isEmpty(): Boolean = set.isEmpty()
+
+    /**
+     * Returns `true` if this set is not empty.
+     */
+    public inline fun isNotEmpty(): Boolean = set.isNotEmpty()
+
+    /**
+     * Returns the first element in the collection.
+     * @throws NoSuchElementException if the collection is empty
+     */
+    public inline fun first(): VALUE_CLASS = VALUE_CLASS(set.first()TO_PARAM)
+
+    /**
+     * Returns the first element in the collection for which [predicate] returns `true`.
+     *
+     * **Note** There is no mechanism for both determining if there is an element that matches
+     * [predicate] _and_ returning it if it exists. Developers should use [forEach] to achieve
+     * this behavior.
+     *
+     * @param predicate Called on elements of the set, returning `true` for an element that matches
+     * or `false` if it doesn't
+     * @return An element in the set for which [predicate] returns `true`.
+     * @throws NoSuchElementException if [predicate] returns `false` for all elements or the
+     * collection is empty.
+     */
+    public inline fun first(predicate: (element: VALUE_CLASS) -> Boolean): VALUE_CLASS =
+        VALUE_CLASS(set.first { predicate(VALUE_CLASS(itTO_PARAM)) }TO_PARAM)
+
+    /**
+     * Iterates over every element stored in this set by invoking
+     * the specified [block] lambda.
+     * @param block called with each element in the set
+     */
+    public inline fun forEach(block: (element: VALUE_CLASS) -> Unit) {
+        contract { callsInPlace(block) }
+        set.forEach { block(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns true if all elements match the given [predicate].
+     * @param predicate called for elements in the set to determine if it returns return `true` for
+     * all elements.
+     */
+    public inline fun all(predicate: (element: VALUE_CLASS) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        return set.all { predicate(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns true if at least one element matches the given [predicate].
+     * @param predicate called for elements in the set to determine if it returns `true` for any
+     * elements.
+     */
+    public inline fun any(predicate: (element: VALUE_CLASS) -> Boolean): Boolean {
+        contract { callsInPlace(predicate) }
+        return set.any { predicate(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns the number of elements in this set.
+     */
+    @androidx.annotation.IntRange(from = 0)
+    public inline fun count(): Int = set.count()
+
+    /**
+     * Returns the number of elements matching the given [predicate].
+     * @param predicate Called for all elements in the set to count the number for which it returns
+     * `true`.
+     */
+    @androidx.annotation.IntRange(from = 0)
+    public inline fun count(predicate: (element: VALUE_CLASS) -> Boolean): Int {
+        contract { callsInPlace(predicate) }
+        return set.count { predicate(VALUE_CLASS(itTO_PARAM)) }
+    }
+
+    /**
+     * Returns `true` if the specified [element] is present in this set, `false`
+     * otherwise.
+     * @param element The element to look for in this set
+     */
+    public inline operator fun contains(element: VALUE_CLASS): Boolean =
+        set.contains(element.BACKING_PROPERTY)
+
+    /**
+     * Returns a string representation of this set. The set is denoted in the
+     * string by the `{}`. Each element is separated by `, `.
+     */
+    public override fun toString(): String = asVALUE_CLASSSet().toString()
+
+    /**
+     * Creates a new [MutableVALUE_CLASSSet]
+     * @param initialCapacity The initial desired capacity for this container.
+     * The container will honor this value by guaranteeing its internal structures
+     * can hold that many elements without requiring any allocations. The initial
+     * capacity can be set to 0.
+     */
+    public constructor(initialCapacity: Int = 6) : this(MutablePRIMITIVESet(initialCapacity))
+
+    /**
+     * Returns a read-only interface to the set.
+     */
+    public inline fun asVALUE_CLASSSet(): VALUE_CLASSSet = VALUE_CLASSSet(set)
+
+    /**
+     * Adds the specified element to the set.
+     * @param element The element to add to the set.
+     * @return `true` if the element has been added or `false` if the element is already
+     * contained within the set.
+     */
+    public inline fun add(element: VALUE_CLASS): Boolean = set.add(element.BACKING_PROPERTY)
+
+    /**
+     * Adds the specified element to the set.
+     * @param element The element to add to the set.
+     */
+    public inline operator fun plusAssign(element: VALUE_CLASS) =
+        set.plusAssign(element.BACKING_PROPERTY)
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     * @param elements A [VALUE_CLASSSet] of elements to add to this set.
+     * @return `true` if any of the specified elements were added to the collection,
+     * `false` if the collection was not modified.
+     */
+    public inline fun addAll(elements: VALUE_CLASSSet): Boolean = set.addAll(elements.set)
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     * @param elements A [VALUE_CLASSSet] of elements to add to this set.
+     * @return `true` if any of the specified elements were added to the collection,
+     * `false` if the collection was not modified.
+     */
+    public inline fun addAll(elements: MutableVALUE_CLASSSet): Boolean = set.addAll(elements.set)
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     * @param elements A [VALUE_CLASSSet] of elements to add to this set.
+     */
+    public inline operator fun plusAssign(elements: VALUE_CLASSSet) =
+        set.plusAssign(elements.set)
+
+    /**
+     * Adds all the elements in the [elements] set into this set.
+     * @param elements A [VALUE_CLASSSet] of elements to add to this set.
+     */
+    public inline operator fun plusAssign(elements: MutableVALUE_CLASSSet) =
+        set.plusAssign(elements.set)
+
+    /**
+     * Removes the specified [element] from the set.
+     * @param element The element to remove from the set.
+     * @return `true` if the [element] was present in the set, or `false` if it wasn't
+     * present before removal.
+     */
+    public inline fun remove(element: VALUE_CLASS): Boolean = set.remove(element.BACKING_PROPERTY)
+
+    /**
+     * Removes the specified [element] from the set if it is present.
+     * @param element The element to remove from the set.
+     */
+    public inline operator fun minusAssign(element: VALUE_CLASS) =
+        set.minusAssign(element.BACKING_PROPERTY)
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     * @param elements An [VALUE_CLASSSet] of elements to be removed from the set.
+     * @return `true` if the set was changed or `false` if none of the elements were present.
+     */
+    public inline fun removeAll(elements: VALUE_CLASSSet): Boolean = set.removeAll(elements.set)
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     * @param elements An [VALUE_CLASSSet] of elements to be removed from the set.
+     * @return `true` if the set was changed or `false` if none of the elements were present.
+     */
+    public inline fun removeAll(elements: MutableVALUE_CLASSSet): Boolean =
+        set.removeAll(elements.set)
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     * @param elements An [VALUE_CLASSSet] of elements to be removed from the set.
+     */
+    public inline operator fun minusAssign(elements: VALUE_CLASSSet) =
+        set.minusAssign(elements.set)
+
+    /**
+     * Removes the specified [elements] from the set, if present.
+     * @param elements An [VALUE_CLASSSet] of elements to be removed from the set.
+     */
+    public inline operator fun minusAssign(elements: MutableVALUE_CLASSSet) =
+        set.minusAssign(elements.set)
+
+    /**
+     * Removes all elements from this set.
+     */
+    public inline fun clear() = set.clear()
+
+    /**
+     * Trims this [MutableVALUE_CLASSSet]'s storage so it is sized appropriately
+     * to hold the current elements.
+     *
+     * Returns the number of empty elements removed from this set's storage.
+     * Returns 0 if no trimming is necessary or possible.
+     */
+    @androidx.annotation.IntRange(from = 0)
+    public inline fun trim(): Int = set.trim()
+}
diff --git a/collection/collection/template/generateCollections.sh b/collection/collection/template/generateCollections.sh
new file mode 100755
index 0000000..39db416d
--- /dev/null
+++ b/collection/collection/template/generateCollections.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+
+primitives=("Float" "Long" "Int")
+suffixes=("f" "L" "")
+
+scriptDir=`dirname ${PWD}/${0}`
+
+for index in ${!primitives[@]}
+do
+  primitive=${primitives[$index]}
+  firstLower=`echo ${primitive:0:1} | tr '[:upper:]' '[:lower:]'`
+  lower="${firstLower}${primitive:1}"
+  echo "generating ${primitive}ObjectMap.kt"
+  sed -e "s/PKey/${primitive}/g" -e "s/pKey/${lower}/g" ${scriptDir}/PKeyObjectMap.kt.template > ${scriptDir}/../src/commonMain/kotlin/androidx/collection/${primitive}ObjectMap.kt
+  echo "generating ${primitive}ObjectMapTest.kt"
+  sed -e "s/PValue/${primitive}/g" ${scriptDir}/ObjectPValueMap.kt.template > ${scriptDir}/../src/commonMain/kotlin/androidx/collection/Object${primitive}Map.kt
+
+  suffix=${suffixes[$index]}
+  echo "generating Object${primitive}Map.kt"
+  sed -e "s/PValue/${primitive}/g" -e "s/ValueSuffix/${suffix}/g" ${scriptDir}/ObjectPValueMapTest.kt.template > ${scriptDir}/../src/commonTest/kotlin/androidx/collection/Object${primitive}MapTest.kt
+  echo "generating Object${primitive}MapTest.kt"
+  sed -e "s/PKey/${primitive}/g" -e"s/pKey/${lower}/g" -e "s/KeySuffix/${suffix}/g" ${scriptDir}/PKeyObjectMapTest.kt.template > ${scriptDir}/../src/commonTest/kotlin/androidx/collection/${primitive}ObjectMapTest.kt
+
+  echo "generating ${primitive}Set.kt"
+  sed -e "s/PKey/${primitive}/g" -e"s/pKey/${lower}/g" ${scriptDir}/PKeySet.kt.template > ${scriptDir}/../src/commonMain/kotlin/androidx/collection/${primitive}Set.kt
+  echo "generating ${primitive}SetTest.kt"
+  sed -e "s/PKey/${primitive}/g" -e"s/pKey/${lower}/g" -e "s/KeySuffix/${suffix}/g" ${scriptDir}/PKeySetTest.kt.template > ${scriptDir}/../src/commonTest/kotlin/androidx/collection/${primitive}SetTest.kt
+
+  echo "generating ${primitive}List.kt"
+  sed -e "s/PKey/${primitive}/g" -e"s/pKey/${lower}/g" ${scriptDir}/PKeyList.kt.template > ${scriptDir}/../src/commonMain/kotlin/androidx/collection/${primitive}List.kt
+  echo "generating ${primitive}ListTest.kt"
+  sed -e "s/PKey/${primitive}/g" -e"s/pKey/${lower}/g" -e "s/KeySuffix/${suffix}/g" ${scriptDir}/PKeyListTest.kt.template > ${scriptDir}/../src/commonTest/kotlin/androidx/collection/${primitive}ListTest.kt
+done
+
+for keyIndex in ${!primitives[@]}
+do
+  key=${primitives[$keyIndex]}
+  firstLower=`echo ${key:0:1} | tr '[:upper:]' '[:lower:]'`
+  lowerKey="${firstLower}${key:1}"
+  keySuffix=${suffixes[$keyIndex]}
+  for valueIndex in ${!primitives[@]}
+  do
+    value=${primitives[$valueIndex]}
+    valueSuffix=${suffixes[$valueIndex]}
+    echo "generating ${key}${value}Map.kt"
+    sed -e "s/PKey/${key}/g" -e "s/pKey/${lowerKey}/g" -e "s/PValue/${value}/g" ${scriptDir}/PKeyPValueMap.kt.template > ${scriptDir}/../src/commonMain/kotlin/androidx/collection/${key}${value}Map.kt
+    echo "generating ${key}${value}MapTest.kt"
+    sed -e "s/PKey/${key}/g" -e "s/pKey/${lowerKey}/g" -e "s/PValue/${value}/g" -e "s/ValueSuffix/${valueSuffix}/g" -e "s/KeySuffix/${keySuffix}/g" ${scriptDir}/PKeyPValueMapTest.kt.template > ${scriptDir}/../src/commonTest/kotlin/androidx/collection/${key}${value}MapTest.kt
+  done
+done
diff --git a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
index 2767e5aa..67921dc 100644
--- a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
+++ b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
@@ -28,11 +28,13 @@
 import androidx.compose.animation.core.updateTransition
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Surface
 import androidx.compose.material3.TabRow
@@ -1115,6 +1117,77 @@
     }
 
     @Test
+    fun testRightEnterExitTransitionIsChosenDuringInterruption() {
+        var flag by mutableStateOf(false)
+        var fixedPosition: Offset? = null
+        var slidePosition: Offset? = null
+        rule.setContent {
+            AnimatedContent(
+                targetState = flag,
+                label = "",
+                transitionSpec = {
+                    if (false isTransitioningTo true) {
+                        ContentTransform(
+                            targetContentEnter = EnterTransition.None,
+                            initialContentExit = slideOutOfContainer(
+                                AnimatedContentTransitionScope.SlideDirection.Start,
+                                animationSpec = tween(durationMillis = 500)
+                            ),
+                            targetContentZIndex = -1.0f,
+                            sizeTransform = SizeTransform(clip = false)
+                        )
+                    } else {
+                        ContentTransform(
+                            targetContentEnter = slideIntoContainer(
+                                AnimatedContentTransitionScope.SlideDirection.End
+                            ),
+                            initialContentExit = ExitTransition.Hold,
+                            targetContentZIndex = 0.0f,
+                            sizeTransform = SizeTransform(clip = false)
+                        )
+                    }
+                },
+                modifier = Modifier.fillMaxSize()
+            ) { flag ->
+                Spacer(
+                    modifier = Modifier
+                        .wrapContentSize(Alignment.Center)
+                        .size(256.dp)
+                        .onGloballyPositioned {
+                            if (flag) {
+                                fixedPosition = it.positionInRoot()
+                            } else {
+                                slidePosition = it.positionInRoot()
+                            }
+                        }
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            flag = true
+        }
+        rule.waitUntil { fixedPosition != null }
+        val initialFixedPosition = fixedPosition
+        // Advance 10 frames
+        repeat(10) {
+            val lastSlidePos = slidePosition
+            rule.waitUntil { slidePosition != lastSlidePos }
+            assertEquals(initialFixedPosition, fixedPosition)
+        }
+
+        // Change the target state amid transition, creating an interruption
+        flag = false
+        // Advance 10 frames
+        repeat(10) {
+            val lastSlidePos = slidePosition
+            rule.waitUntil { slidePosition != lastSlidePos }
+            assertEquals(initialFixedPosition, fixedPosition)
+        }
+        rule.waitForIdle()
+    }
+
+    @Test
     fun testScaleToFitWithFitHeight() {
         var target by mutableStateOf(true)
         var box1Coords: LayoutCoordinates? = null
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 b82c044..e557851 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
@@ -820,13 +820,14 @@
         }
         if (!contentMap.containsKey(targetState) || !contentMap.containsKey(currentState)) {
             contentMap.clear()
-            val enter = transitionSpec(rootScope).targetContentEnter
-            val exit = rootScope.transitionSpec().initialContentExit
-            val zIndex = transitionSpec(rootScope).targetContentZIndex
             currentlyVisible.fastForEach { stateForContent ->
                 contentMap[stateForContent] = {
+                    // Only update content transform when enter/exit _direction_ changes.
+                    val contentTransform = remember(stateForContent == targetState) {
+                        rootScope.transitionSpec()
+                    }
                     PopulateContentFor(
-                        stateForContent, rootScope, enter, exit, zIndex, currentlyVisible, content
+                        stateForContent, rootScope, contentTransform, currentlyVisible, content
                     )
                 }
             }
@@ -871,33 +872,32 @@
 private inline fun <S> Transition<S>.PopulateContentFor(
     stateForContent: S,
     rootScope: AnimatedContentRootScope<S>,
-    enter: EnterTransition,
-    exit: ExitTransition,
-    zIndex: Float,
+    contentTransform: ContentTransform,
     currentlyVisible: SnapshotStateList<S>,
     crossinline content: @Composable() AnimatedContentScope.(targetState: S) -> Unit
 ) {
-    var activeEnter by remember { mutableStateOf(enter) }
+    var activeEnter by remember { mutableStateOf(contentTransform.targetContentEnter) }
     var activeExit by remember { mutableStateOf(ExitTransition.None) }
-    val targetZIndex = remember { zIndex }
+    val targetZIndex = remember { contentTransform.targetContentZIndex }
 
     val isEntering = targetState == stateForContent
     if (targetState == currentState) {
         // Transition finished, reset active enter & exit.
-        activeEnter = androidx.compose.animation.EnterTransition.None
-        activeExit = androidx.compose.animation.ExitTransition.None
+        activeEnter = EnterTransition.None
+        activeExit = ExitTransition.None
     } else if (isEntering) {
         // If the previous enter transition never finishes when multiple
         // interruptions happen, avoid adding new enter transitions for simplicity.
-        if (activeEnter == androidx.compose.animation.EnterTransition.None)
-            activeEnter += enter
+        if (activeEnter == EnterTransition.None)
+            activeEnter += contentTransform.targetContentEnter
     } else {
         // If the previous exit transition never finishes when multiple
         // interruptions happen, avoid adding new enter transitions for simplicity.
-        if (activeExit == androidx.compose.animation.ExitTransition.None) {
-            activeExit += exit
+        if (activeExit == ExitTransition.None) {
+            activeExit += contentTransform.initialContentExit
         }
     }
+
     val childData = remember { AnimatedContentRootScope.ChildData(stateForContent) }
     AnimatedEnterExitImpl(
         this,
@@ -915,16 +915,15 @@
             .then(
                 if (isEntering) {
                     activeEnter[ScaleToFitTransitionKey]
-                        ?: activeExit[ScaleToFitTransitionKey] ?: androidx.compose.ui.Modifier
+                        ?: activeExit[ScaleToFitTransitionKey] ?: Modifier
                 } else {
                     activeExit[ScaleToFitTransitionKey]
-                        ?: activeEnter[ScaleToFitTransitionKey] ?: androidx.compose.ui.Modifier
+                        ?: activeEnter[ScaleToFitTransitionKey] ?: Modifier
                 }
             ),
         shouldDisposeBlock = { currentState, targetState ->
-            currentState == androidx.compose.animation.EnterExitState.PostExit &&
-                targetState == androidx.compose.animation.EnterExitState.PostExit &&
-                !activeExit.data.hold
+            currentState == EnterExitState.PostExit &&
+                targetState == EnterExitState.PostExit && !activeExit.data.hold
         },
         onLookaheadMeasured = {
             if (isEntering) rootScope.targetSizeMap.getOrPut(targetState) {
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
index d428ebd..2eaec85 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
@@ -302,7 +302,8 @@
                 }
                 A(%composer, 0)
                 M3({ %composer: Composer?, %changed: Int ->
-                  sourceInformationMarkerStart(%composer, <>, "C<A()>,<A()>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
                   A(%composer, 0)
                   if (condition) {
                     %composer.endToMarker(tmp0_marker)
@@ -315,7 +316,7 @@
                     return
                   }
                   A(%composer, 0)
-                  sourceInformationMarkerEnd(%composer)
+                  %composer.endReplaceableGroup()
                 }, %composer, 0)
                 A(%composer, 0)
                 if (isTraceInProgress()) {
@@ -374,7 +375,8 @@
                 }
                 A(%composer, 0)
                 M3({ %composer: Composer?, %changed: Int ->
-                  sourceInformationMarkerStart(%composer, <>, "C<A()>,<A()>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
                   A(%composer, 0)
                   if (a) {
                     %composer.endToMarker(tmp0_marker)
@@ -387,10 +389,11 @@
                     return
                   }
                   A(%composer, 0)
-                  sourceInformationMarkerEnd(%composer)
+                  %composer.endReplaceableGroup()
                 }, %composer, 0)
                 M3({ %composer: Composer?, %changed: Int ->
-                  sourceInformationMarkerStart(%composer, <>, "C<A()>,<A()>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
                   A(%composer, 0)
                   if (b) {
                     %composer.endToMarker(tmp0_marker)
@@ -403,7 +406,7 @@
                     return
                   }
                   A(%composer, 0)
-                  sourceInformationMarkerEnd(%composer)
+                  %composer.endReplaceableGroup()
                 }, %composer, 0)
                 A(%composer, 0)
                 if (isTraceInProgress()) {
@@ -451,14 +454,15 @@
                 }
                 A(%composer, 0)
                 M3({ %composer: Composer?, %changed: Int ->
-                  sourceInformationMarkerStart(%composer, <>, "C<A()>,<A()>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
                   A(%composer, 0)
                   if (condition) {
-                    sourceInformationMarkerEnd(%composer)
+                    %composer.endReplaceableGroup()
                     return@M3
                   }
                   A(%composer, 0)
-                  sourceInformationMarkerEnd(%composer)
+                  %composer.endReplaceableGroup()
                 }, %composer, 0)
                 A(%composer, 0)
                 if (isTraceInProgress()) {
@@ -498,7 +502,8 @@
                       traceEventStart(<>, %changed, -1, <>)
                     }
                     M1({ %composer: Composer?, %changed: Int ->
-                      sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
+                      %composer.startReplaceableGroup(<>)
+                      sourceInformation(%composer, "C:Test.kt")
                       if (condition) {
                         %composer.endToMarker(tmp0_marker)
                         if (isTraceInProgress()) {
@@ -506,7 +511,7 @@
                         }
                         return@composableLambdaInstance
                       }
-                      sourceInformationMarkerEnd(%composer)
+                      %composer.endReplaceableGroup()
                     }, %composer, 0)
                     if (isTraceInProgress()) {
                       traceEventEnd()
@@ -572,12 +577,13 @@
                   sourceInformationMarkerStart(%composer, <>, "C<A()>,<M1>,<A()>:Test.kt")
                   A(%composer, 0)
                   M1({ %composer: Composer?, %changed: Int ->
-                    sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
+                    %composer.startReplaceableGroup(<>)
+                    sourceInformation(%composer, "C:Test.kt")
                     if (condition) {
-                      sourceInformationMarkerEnd(%composer)
+                      %composer.endReplaceableGroup()
                       return@M1
                     }
-                    sourceInformationMarkerEnd(%composer)
+                    %composer.endReplaceableGroup()
                   }, %composer, 0)
                   A(%composer, 0)
                   sourceInformationMarkerEnd(%composer)
@@ -630,18 +636,20 @@
                 A(%composer, 0)
                 M3({ %composer: Composer?, %changed: Int ->
                   val tmp0_marker = %composer.currentMarker
-                  sourceInformationMarkerStart(%composer, <>, "C<A()>,<M1>,<A()>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<M1>,<A()>:Test.kt")
                   A(%composer, 0)
                   M1({ %composer: Composer?, %changed: Int ->
-                    sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
+                    %composer.startReplaceableGroup(<>)
+                    sourceInformation(%composer, "C:Test.kt")
                     if (condition) {
                       %composer.endToMarker(tmp0_marker)
                       return@M3
                     }
-                    sourceInformationMarkerEnd(%composer)
+                    %composer.endReplaceableGroup()
                   }, %composer, 0)
                   A(%composer, 0)
-                  sourceInformationMarkerEnd(%composer)
+                  %composer.endReplaceableGroup()
                 }, %composer, 0)
                 A(%composer, 0)
                 if (isTraceInProgress()) {
@@ -694,7 +702,8 @@
                 }
                 A(%composer, 0)
                 M1({ %composer: Composer?, %changed: Int ->
-                  sourceInformationMarkerStart(%composer, <>, "C<A()>,<A()>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
                   A(%composer, 0)
                   %composer.startReplaceableGroup(<>)
                   sourceInformation(%composer, "*<A()>,<A()>")
@@ -714,7 +723,7 @@
                   }
                   %composer.endReplaceableGroup()
                   A(%composer, 0)
-                  sourceInformationMarkerEnd(%composer)
+                  %composer.endReplaceableGroup()
                 }, %composer, 0)
                 A(%composer, 0)
                 if (isTraceInProgress()) {
@@ -769,24 +778,26 @@
                 }
                 A(%composer, 0)
                 M3({ %composer: Composer?, %changed: Int ->
-                  sourceInformationMarkerStart(%composer, <>, "C<A()>,<A()>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
                   A(%composer, 0)
                   if (condition) {
-                    sourceInformationMarkerEnd(%composer)
+                    %composer.endReplaceableGroup()
                     return@M3
                   }
                   A(%composer, 0)
-                  sourceInformationMarkerEnd(%composer)
+                  %composer.endReplaceableGroup()
                 }, %composer, 0)
                 M3({ %composer: Composer?, %changed: Int ->
-                  sourceInformationMarkerStart(%composer, <>, "C<A()>,<A()>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
                   A(%composer, 0)
                   if (condition) {
-                    sourceInformationMarkerEnd(%composer)
+                    %composer.endReplaceableGroup()
                     return@M3
                   }
                   A(%composer, 0)
-                  sourceInformationMarkerEnd(%composer)
+                  %composer.endReplaceableGroup()
                 }, %composer, 0)
                 A(%composer, 0)
                 if (isTraceInProgress()) {
@@ -839,14 +850,16 @@
                 }
                 Text("Root - before", %composer, 0b0110)
                 M1({ %composer: Composer?, %changed: Int ->
-                  sourceInformationMarkerStart(%composer, <>, "C<Text("...>,<Text("...>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
                   Text("M1 - begin", %composer, 0b0110)
                   %composer.startReplaceableGroup(<>)
                   sourceInformation(%composer, "<Text("...>,<M1>")
                   if (condition) {
                     Text("if - begin", %composer, 0b0110)
                     M1({ %composer: Composer?, %changed: Int ->
-                      sourceInformationMarkerStart(%composer, <>, "C<Text("...>:Test.kt")
+                      %composer.startReplaceableGroup(<>)
+                      sourceInformation(%composer, "C<Text("...>:Test.kt")
                       Text("In CCM1", %composer, 0b0110)
                       %composer.endToMarker(tmp0_marker)
                       if (isTraceInProgress()) {
@@ -856,12 +869,12 @@
                         test_CM1_CCM1_RetFun(condition, %composer, updateChangedFlags(%changed or 0b0001))
                       }
                       return
-                      sourceInformationMarkerEnd(%composer)
+                      %composer.endReplaceableGroup()
                     }, %composer, 0)
                   }
                   %composer.endReplaceableGroup()
                   Text("M1 - end", %composer, 0b0110)
-                  sourceInformationMarkerEnd(%composer)
+                  %composer.endReplaceableGroup()
                 }, %composer, 0)
                 Text("Root - end", %composer, 0b0110)
                 if (isTraceInProgress()) {
@@ -905,13 +918,14 @@
                 traceEventStart(<>, %changed, -1, <>)
               }
               FakeBox({ %composer: Composer?, %changed: Int ->
-                sourceInformationMarkerStart(%composer, <>, "C<A()>:Test.kt")
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "C<A()>:Test.kt")
                 if (condition) {
-                  sourceInformationMarkerEnd(%composer)
+                  %composer.endReplaceableGroup()
                   return@FakeBox
                 }
                 A(%composer, 0)
-                sourceInformationMarkerEnd(%composer)
+                %composer.endReplaceableGroup()
               }, %composer, 0)
               if (isTraceInProgress()) {
                 traceEventEnd()
@@ -1087,13 +1101,14 @@
                   traceEventStart(<>, %dirty, -1, <>)
                 }
                 IW({ %composer: Composer?, %changed: Int ->
-                  sourceInformationMarkerStart(%composer, <>, "C<A()>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>:Test.kt")
                   if (condition) {
-                    sourceInformationMarkerEnd(%composer)
+                    %composer.endReplaceableGroup()
                     return@IW
                   }
                   A(%composer, 0)
-                  sourceInformationMarkerEnd(%composer)
+                  %composer.endReplaceableGroup()
                 }, %composer, 0)
                 if (isTraceInProgress()) {
                   traceEventEnd()
@@ -1189,7 +1204,8 @@
                 }
                 Text("Some text", %composer, 0b0110)
                 M1({ %composer: Composer?, %changed: Int ->
-                  sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C:Test.kt")
                   Identity {
                     if (condition) {
                       %composer.endToMarker(tmp0_marker)
@@ -1202,7 +1218,7 @@
                       return
                     }
                   }
-                  sourceInformationMarkerEnd(%composer)
+                  %composer.endReplaceableGroup()
                 }, %composer, 0)
                 Text("Some more text", %composer, 0b0110)
                 if (isTraceInProgress()) {
@@ -1247,14 +1263,15 @@
                 }
                 Text("Some text", %composer, 0b0110)
                 M1({ %composer: Composer?, %changed: Int ->
-                  sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C:Test.kt")
                   Identity {
                     if (condition) {
-                      sourceInformationMarkerEnd(%composer)
+                      %composer.endReplaceableGroup()
                       return@M1
                     }
                   }
-                  sourceInformationMarkerEnd(%composer)
+                  %composer.endReplaceableGroup()
                 }, %composer, 0)
                 Text("Some more text", %composer, 0b0110)
                 if (isTraceInProgress()) {
@@ -1271,6 +1288,154 @@
     )
 
     @Test
+    fun verifyEarlyExitFromNestedInlineFunction() = verifyComposeIrTransform(
+        source = """
+            import androidx.compose.runtime.*
+
+            @Composable
+            @NonRestartableComposable
+            fun Test(condition: Boolean) {
+                Text("Before outer")
+                InlineLinearA {
+                    Text("Before inner")
+                    InlineLinearB inner@{
+                        Text("Before return")
+                        if (condition) return@inner
+                        Text("After return")
+                    }
+                    Text("After inner")
+                }
+                Text("Before outer")
+            }
+        """,
+        expectedTransformed = """
+            @Composable
+            @NonRestartableComposable
+            fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Test)<Text("...>,<Inline...>,<Text("...>:Test.kt")
+              if (isTraceInProgress()) {
+                traceEventStart(<>, %changed, -1, <>)
+              }
+              Text("Before outer", %composer, 0b0110)
+              InlineLinearA({ %composer: Composer?, %changed: Int ->
+                sourceInformationMarkerStart(%composer, <>, "C<Text("...>,<Inline...>,<Text("...>:Test.kt")
+                Text("Before inner", %composer, 0b0110)
+                InlineLinearB({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
+                  Text("Before return", %composer, 0b0110)
+                  if (condition) {
+                    %composer.endReplaceableGroup()
+                    return@InlineLinearB
+                  }
+                  Text("After return", %composer, 0b0110)
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                Text("After inner", %composer, 0b0110)
+                sourceInformationMarkerEnd(%composer)
+              }, %composer, 0)
+              Text("Before outer", %composer, 0b0110)
+              if (isTraceInProgress()) {
+                traceEventEnd()
+              }
+              %composer.endReplaceableGroup()
+            }
+        """,
+        extra = """
+            import androidx.compose.runtime.*
+
+            @Composable
+            fun Text(value: String) { }
+
+            @Composable
+            inline fun InlineLinearA(content: @Composable () -> Unit) {
+                content()
+            }
+
+            @Composable
+            inline fun InlineLinearB(content: @Composable () -> Unit) {
+                content()
+            }
+        """
+    )
+
+    @Test
+    fun verifyEarlyExitFromMultiLevelNestedInlineFunction() = verifyComposeIrTransform(
+        source = """
+            import androidx.compose.runtime.*
+
+            @Composable
+            @NonRestartableComposable
+            fun Test(condition: Boolean) {
+                Text("Before outer")
+                InlineLinearA outer@{
+                    Text("Before inner")
+                    InlineLinearB {
+                        Text("Before return")
+                        if (condition) return@outer
+                        Text("After return")
+                    }
+                    Text("After inner")
+                }
+                Text("Before outer")
+            }
+        """,
+        expectedTransformed = """
+            @Composable
+            @NonRestartableComposable
+            fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Test)<Text("...>,<Inline...>,<Text("...>:Test.kt")
+              if (isTraceInProgress()) {
+                traceEventStart(<>, %changed, -1, <>)
+              }
+              Text("Before outer", %composer, 0b0110)
+              InlineLinearA({ %composer: Composer?, %changed: Int ->
+                val tmp0_marker = %composer.currentMarker
+                %composer.startReplaceableGroup(<>)
+                sourceInformation(%composer, "C<Text("...>,<Inline...>,<Text("...>:Test.kt")
+                Text("Before inner", %composer, 0b0110)
+                InlineLinearB({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
+                  Text("Before return", %composer, 0b0110)
+                  if (condition) {
+                    %composer.endToMarker(tmp0_marker)
+                    return@InlineLinearA
+                  }
+                  Text("After return", %composer, 0b0110)
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                Text("After inner", %composer, 0b0110)
+                %composer.endReplaceableGroup()
+              }, %composer, 0)
+              Text("Before outer", %composer, 0b0110)
+              if (isTraceInProgress()) {
+                traceEventEnd()
+              }
+              %composer.endReplaceableGroup()
+            }
+        """,
+        extra = """
+            import androidx.compose.runtime.*
+
+            @Composable
+            fun Text(value: String) { }
+
+            @Composable
+            inline fun InlineLinearA(content: @Composable () -> Unit) {
+                content()
+            }
+
+            @Composable
+            inline fun InlineLinearB(content: @Composable () -> Unit) {
+                content()
+            }
+        """
+    )
+
+    @Test
     fun testEnsureRuntimeTestWillCompile_CL() {
         classLoader(
             """
@@ -1332,7 +1497,8 @@
                 }
                 Text("Root - before", %composer, 0b0110)
                 M1({ %composer: Composer?, %changed: Int ->
-                  sourceInformationMarkerStart(%composer, <>, "C<Text("...>,<Text("...>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<Text("...>,<Text("...>:Test.kt")
                   Text("M1 - before", %composer, 0b0110)
                   if (condition) {
                     %composer.endToMarker(tmp0_marker)
@@ -1345,7 +1511,7 @@
                     return
                   }
                   Text("M1 - after", %composer, 0b0110)
-                  sourceInformationMarkerEnd(%composer)
+                  %composer.endReplaceableGroup()
                 }, %composer, 0)
                 Text("Root - after", %composer, 0b0110)
                 if (isTraceInProgress()) {
@@ -6182,16 +6348,18 @@
                 }
                 Inline1({ %composer: Composer?, %changed: Int ->
                   val tmp0_marker = %composer.currentMarker
-                  sourceInformationMarkerStart(%composer, <>, "C<Inline...>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<Inline...>:Test.kt")
                   Inline2({ %composer: Composer?, %changed: Int ->
-                    sourceInformationMarkerStart(%composer, <>, "C:Test.kt")
+                    %composer.startReplaceableGroup(<>)
+                    sourceInformation(%composer, "C:Test.kt")
                     if (true) {
                       %composer.endToMarker(tmp0_marker)
                       return@Inline1
                     }
-                    sourceInformationMarkerEnd(%composer)
+                    %composer.endReplaceableGroup()
                   }, %composer, 0)
-                  sourceInformationMarkerEnd(%composer)
+                  %composer.endReplaceableGroup()
                 }, %composer, 0)
                 if (isTraceInProgress()) {
                   traceEventEnd()
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt
index 4fb99f6..a3a2934 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTestsNoSource.kt
@@ -174,4 +174,77 @@
             }
         """
     )
+
+    @Test
+    fun verifyEarlyExitFromMultiLevelNestedInlineFunction() = verifyComposeIrTransform(
+        source = """
+            import androidx.compose.runtime.*
+
+            @Composable
+            @NonRestartableComposable
+            fun Test(condition: Boolean) {
+                Text("Before outer")
+                InlineLinearA outer@{
+                    Text("Before inner")
+                    InlineLinearB {
+                        Text("Before return")
+                        if (condition) return@outer
+                        Text("After return")
+                    }
+                    Text("After inner")
+                }
+                Text("Before outer")
+            }
+        """,
+        expectedTransformed = """
+            @Composable
+            @NonRestartableComposable
+            fun Test(condition: Boolean, %composer: Composer?, %changed: Int) {
+              %composer.startReplaceableGroup(<>)
+              sourceInformation(%composer, "C(Test)")
+              if (isTraceInProgress()) {
+                traceEventStart(<>, %changed, -1, <>)
+              }
+              Text("Before outer", %composer, 0b0110)
+              InlineLinearA({ %composer: Composer?, %changed: Int ->
+                val tmp0_marker = %composer.currentMarker
+                %composer.startReplaceableGroup(<>)
+                Text("Before inner", %composer, 0b0110)
+                InlineLinearB({ %composer: Composer?, %changed: Int ->
+                  %composer.startReplaceableGroup(<>)
+                  Text("Before return", %composer, 0b0110)
+                  if (condition) {
+                    %composer.endToMarker(tmp0_marker)
+                    return@InlineLinearA
+                  }
+                  Text("After return", %composer, 0b0110)
+                  %composer.endReplaceableGroup()
+                }, %composer, 0)
+                Text("After inner", %composer, 0b0110)
+                %composer.endReplaceableGroup()
+              }, %composer, 0)
+              Text("Before outer", %composer, 0b0110)
+              if (isTraceInProgress()) {
+                traceEventEnd()
+              }
+              %composer.endReplaceableGroup()
+            }
+        """,
+        extra = """
+            import androidx.compose.runtime.*
+
+            @Composable
+            fun Text(value: String) { }
+
+            @Composable
+            inline fun InlineLinearA(content: @Composable () -> Unit) {
+                content()
+            }
+
+            @Composable
+            inline fun InlineLinearB(content: @Composable () -> Unit) {
+                content()
+            }
+        """
+    )
 }
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt
index 2aa05c6..aaaba3b2 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/TraceInformationTest.kt
@@ -175,7 +175,8 @@
                 }
                 A(%composer, 0)
                 Wrapper({ %composer: Composer?, %changed: Int ->
-                  sourceInformationMarkerStart(%composer, <>, "C<A()>,<A()>:Test.kt")
+                  %composer.startReplaceableGroup(<>)
+                  sourceInformation(%composer, "C<A()>,<A()>:Test.kt")
                   A(%composer, 0)
                   if (!condition) {
                     %composer.endToMarker(tmp0_marker)
@@ -188,7 +189,7 @@
                     return
                   }
                   A(%composer, 0)
-                  sourceInformationMarkerEnd(%composer)
+                  %composer.endReplaceableGroup()
                 }, %composer, 0)
                 A(%composer, 0)
                 if (isTraceInProgress()) {
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
index b1b54c0..1b4c6c2f 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
@@ -978,17 +978,10 @@
         val bodyPreamble = mutableStatementContainer()
         val bodyEpilogue = mutableStatementContainer()
 
-        // First generate the source information call
         val isInlineLambda = scope.isInlinedLambda
-        if (collectSourceInformation) {
-            if (isInlineLambda) {
-                sourceInformationPreamble.statements.add(
-                    irSourceInformationMarkerStart(body, scope)
-                )
-                bodyEpilogue.statements.add(irSourceInformationMarkerEnd(body, scope))
-            } else {
-                sourceInformationPreamble.statements.add(irSourceInformation(scope))
-            }
+
+        if (collectSourceInformation && !isInlineLambda) {
+            sourceInformationPreamble.statements.add(irSourceInformation(scope))
         }
 
         // we start off assuming that we *can* skip execution of the function
@@ -1028,7 +1021,12 @@
         // are using the dispatchReceiverParameter or the extensionReceiverParameter
         val transformed = nonReturningBody.apply {
             transformChildrenVoid()
+        }.let {
+            if (isInlineLambda) {
+                it.asSourceOrEarlyExitGroup(scope)
+            } else it
         }
+
         canSkipExecution = buildPreambleStatementsAndReturnIfSkippingPossible(
             body,
             skipPreamble,
@@ -1058,12 +1056,6 @@
             }
         }
 
-        if (collectSourceInformation && isInlineLambda) {
-            scope.realizeEndCalls {
-                irSourceInformationMarkerEnd(body, scope)
-            }
-        }
-
         if (canSkipExecution) {
             // We CANNOT skip if any of the following conditions are met
             // 1. if any of the stable parameters have *differences* from last execution.
@@ -1290,7 +1282,6 @@
                 returnVar?.let { irReturnVar(declaration.symbol, it) }
             )
         )
-
         scope.metrics.recordFunction(
             composable = true,
             restartable = true,
@@ -2290,7 +2281,7 @@
         }
     }
 
-    fun irTemporary(
+    private fun irTemporary(
         value: IrExpression,
         nameHint: String? = null,
         irType: IrType = value.type,
@@ -2307,7 +2298,9 @@
             name,
             irType,
             isVar
-        )
+        ).also {
+            it.parent = currentFunctionScope.function.parent
+        }
     }
 
     private fun IrBlock.withReplaceableGroupStatements(scope: Scope.BlockScope): IrExpression {
@@ -2404,7 +2397,7 @@
     private fun IrExpression.wrap(
         before: List<IrExpression> = emptyList(),
         after: List<IrExpression> = emptyList()
-    ): IrExpression {
+    ): IrContainerExpression {
         return if (after.isEmpty() || type.isNothing() || type.isUnit()) {
             wrap(startOffset, endOffset, type, before, after)
         } else {
@@ -2425,7 +2418,7 @@
         type: IrType,
         before: List<IrExpression> = emptyList(),
         after: List<IrExpression> = emptyList()
-    ): IrExpression {
+    ): IrContainerExpression {
         return IrBlockImpl(
             startOffset,
             endOffset,
@@ -2462,6 +2455,58 @@
         )
     }
 
+    private fun IrContainerExpression.asSourceOrEarlyExitGroup(
+        scope: Scope.FunctionScope
+    ): IrContainerExpression {
+        if (scope.hasEarlyReturn) {
+            currentFunctionScope.metrics.recordGroup()
+        } else if (!collectSourceInformation) {
+            // If we are not generating source information and the lambda does not contain an
+            // early exit this we don't need a group or source markers.
+            return this
+        }
+        // if the scope has no composable calls, then the only important thing is that a
+        // start/end call gets executed. as a result, we can just put them both at the top of
+        // the group, and we don't have to deal with any of the complicated jump logic that
+        // could be inside of the block
+        val makeStart = {
+            if (scope.hasEarlyReturn) irStartReplaceableGroup(
+                this,
+                scope,
+                startOffset = startOffset,
+                endOffset = endOffset
+            )
+            else irSourceInformationMarkerStart(this, scope)
+        }
+        val makeEnd = {
+            if (scope.hasEarlyReturn) irEndReplaceableGroup(scope = scope)
+            else irSourceInformationMarkerEnd(this, scope)
+        }
+        if (!scope.hasComposableCalls && !scope.hasReturn && !scope.hasJump) {
+            return wrap(
+                before = listOf(makeStart()),
+                after = listOf(makeEnd()),
+            )
+        }
+        scope.realizeGroup(makeEnd)
+        return when {
+            // if the scope ends with a return call, then it will get properly ended if we
+            // just push the end call on the scope because of the way returns get transformed in
+            // this class. As a result, here we can safely just "prepend" the start call
+            endsWithReturnOrJump() -> {
+                wrap(before = listOf(makeStart()))
+            }
+            // otherwise, we want to push an end call for any early returns/jumps, but also add
+            // an end call to the end of the group
+            else -> {
+                wrap(
+                    before = listOf(makeStart()),
+                    after = listOf(makeEnd()),
+                )
+            }
+        }
+    }
+
     private fun mutableStatementContainer() = mutableStatementContainer(context)
 
     private fun encounteredComposableCall(withGroups: Boolean, isCached: Boolean) {
@@ -2574,12 +2619,16 @@
                                 it.markReturn(extraEndLocation)
                             }
                             scope.markReturn(extraEndLocation)
+                            if (scope.isInlinedLambda && scope.inComposableCall) {
+                                scope.hasEarlyReturn = true
+                            }
                         } else {
                             val functionScope = scope
                             val targetScope = currentScope as? Scope.BlockScope ?: functionScope
                             if (functionScope.isInlinedLambda) {
                                 val marker = irGet(functionScope.allocateMarker())
                                 extraEndLocation(irEndToMarker(marker, targetScope))
+                                scope.hasEarlyReturn = true
                             } else {
                                 val marker = functionScope.allocateMarker()
                                 functionScope.markReturn {
@@ -2591,8 +2640,10 @@
                         scope.updateIntrinsiceRememberSafety(false)
                         break@loop
                     }
-                    if (scope.isInlinedLambda && scope.inComposableCall)
+                    if (scope.isInlinedLambda && scope.inComposableCall) {
                         leavingInlinedLambda = true
+                        scope.hasEarlyReturn = true
+                    }
                 }
                 is Scope.BlockScope -> {
                     blockScopeMarks.add(scope)
@@ -3708,6 +3759,8 @@
 
             val metrics: FunctionMetrics = transformer.metricsFor(function)
 
+            var hasEarlyReturn: Boolean = false
+
             private var lastTemporaryIndex: Int = 0
 
             private fun nextTemporaryIndex(): Int = lastTemporaryIndex++
@@ -4038,13 +4091,12 @@
                 makeEnd: () -> IrExpression
             ) {
                 addProvisionalSourceLocations(scope.sourceLocations)
-                coalescableChilds.add(
-                    CoalescableGroupInfo(
-                        scope,
-                        realizeGroup,
-                        makeEnd
-                    )
+                val groupInfo = CoalescableGroupInfo(
+                    scope,
+                    realizeGroup,
+                    makeEnd
                 )
+                coalescableChilds.add(groupInfo)
             }
 
             open fun calculateHasSourceInformation(sourceInformationEnabled: Boolean): Boolean =
@@ -4442,6 +4494,7 @@
                     isConst = false,
                     isLateinit = false
                 ).apply {
+                    parent = currentFunctionScope.function.parent
                     initializer = irGet(param)
                 }
             }
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 284cdd3..361d875 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -2,13 +2,13 @@
 package androidx.compose.foundation {
 
   public final class BackgroundKt {
-    method public static androidx.compose.ui.Modifier background(androidx.compose.ui.Modifier, androidx.compose.ui.graphics.Brush brush, optional androidx.compose.ui.graphics.Shape shape, optional @FloatRange(from=0.0, to=1.0) float alpha);
-    method public static androidx.compose.ui.Modifier background(androidx.compose.ui.Modifier, long color, optional androidx.compose.ui.graphics.Shape shape);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier background(androidx.compose.ui.Modifier, androidx.compose.ui.graphics.Brush brush, optional androidx.compose.ui.graphics.Shape shape, optional @FloatRange(from=0.0, to=1.0) float alpha);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier background(androidx.compose.ui.Modifier, long color, optional androidx.compose.ui.graphics.Shape shape);
   }
 
   public final class BasicMarqueeKt {
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.MarqueeSpacing MarqueeSpacing(float spacing);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier basicMarquee(androidx.compose.ui.Modifier, optional int iterations, optional int animationMode, optional int delayMillis, optional int initialDelayMillis, optional androidx.compose.foundation.MarqueeSpacing spacing, optional float velocity);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier basicMarquee(androidx.compose.ui.Modifier, optional int iterations, optional int animationMode, optional int delayMillis, optional int initialDelayMillis, optional androidx.compose.foundation.MarqueeSpacing spacing, optional float velocity);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static int getDefaultMarqueeDelayMillis();
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static int getDefaultMarqueeIterations();
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.MarqueeSpacing getDefaultMarqueeSpacing();
@@ -28,7 +28,7 @@
 
   public final class BasicTooltipKt {
     method @androidx.compose.runtime.Composable public static void BasicTooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.foundation.BasicTooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method public static androidx.compose.foundation.BasicTooltipState BasicTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
+    method @androidx.compose.runtime.Stable public static androidx.compose.foundation.BasicTooltipState BasicTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
     method @androidx.compose.runtime.Composable public static androidx.compose.foundation.BasicTooltipState rememberBasicTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
   }
 
@@ -47,9 +47,9 @@
   }
 
   public final class BorderKt {
-    method public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, androidx.compose.foundation.BorderStroke border, optional androidx.compose.ui.graphics.Shape shape);
-    method public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, float width, androidx.compose.ui.graphics.Brush brush, androidx.compose.ui.graphics.Shape shape);
-    method public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, float width, long color, optional androidx.compose.ui.graphics.Shape shape);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, androidx.compose.foundation.BorderStroke border, optional androidx.compose.ui.graphics.Shape shape);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, float width, androidx.compose.ui.graphics.Brush brush, androidx.compose.ui.graphics.Shape shape);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, float width, long color, optional androidx.compose.ui.graphics.Shape shape);
   }
 
   @androidx.compose.runtime.Immutable public final class BorderStroke {
@@ -83,7 +83,7 @@
   }
 
   public final class ClipScrollableContainerKt {
-    method public static androidx.compose.ui.Modifier clipScrollableContainer(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.Orientation orientation);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier clipScrollableContainer(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.Orientation orientation);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface CombinedClickableNode extends androidx.compose.ui.node.PointerInputModifierNode {
@@ -103,7 +103,7 @@
   }
 
   public final class FocusableKt {
-    method public static androidx.compose.ui.Modifier focusGroup(androidx.compose.ui.Modifier);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier focusGroup(androidx.compose.ui.Modifier);
     method public static androidx.compose.ui.Modifier focusable(androidx.compose.ui.Modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
@@ -111,6 +111,30 @@
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier onFocusedBoundsChanged(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.LayoutCoordinates,kotlin.Unit> onPositioned);
   }
 
+  public final class GraphicsSurfaceKt {
+    method @androidx.compose.runtime.Composable public static void EmbeddedGraphicsSurface(optional androidx.compose.ui.Modifier modifier, optional boolean isOpaque, optional long surfaceSize, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.GraphicsSurfaceScope,kotlin.Unit> onInit);
+    method @androidx.compose.runtime.Composable public static void GraphicsSurface(optional androidx.compose.ui.Modifier modifier, optional boolean isOpaque, optional int zOrder, optional long surfaceSize, optional boolean isSecure, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.GraphicsSurfaceScope,kotlin.Unit> onInit);
+  }
+
+  public interface GraphicsSurfaceScope {
+    method public void onSurface(kotlin.jvm.functions.Function5<? super androidx.compose.foundation.SurfaceCoroutineScope,? super android.view.Surface,? super java.lang.Integer,? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onSurface);
+  }
+
+  @kotlin.jvm.JvmInline public final value class GraphicsSurfaceZOrder {
+    method public int getZOrder();
+    property public final int zOrder;
+    field public static final androidx.compose.foundation.GraphicsSurfaceZOrder.Companion Companion;
+  }
+
+  public static final class GraphicsSurfaceZOrder.Companion {
+    method public int getBehind();
+    method public int getMediaOverlay();
+    method public int getOnTop();
+    property public final int Behind;
+    property public final int MediaOverlay;
+    property public final int OnTop;
+  }
+
   public final class HoverableKt {
     method public static androidx.compose.ui.Modifier hoverable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional boolean enabled);
   }
@@ -140,21 +164,7 @@
   }
 
   public final class MagnifierKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier magnifier(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Density,androidx.compose.ui.geometry.Offset> sourceCenter, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Density,androidx.compose.ui.geometry.Offset> magnifierCenter, optional float zoom, optional androidx.compose.foundation.MagnifierStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpSize,kotlin.Unit>? onSizeChanged);
-  }
-
-  @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public final class MagnifierStyle {
-    ctor @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public MagnifierStyle(optional long size, optional float cornerRadius, optional float elevation, optional boolean clippingEnabled, optional boolean fishEyeEnabled);
-    method public boolean isSupported();
-    property public final boolean isSupported;
-    field public static final androidx.compose.foundation.MagnifierStyle.Companion Companion;
-  }
-
-  public static final class MagnifierStyle.Companion {
-    method public androidx.compose.foundation.MagnifierStyle getDefault();
-    method public androidx.compose.foundation.MagnifierStyle getTextDefault();
-    property public final androidx.compose.foundation.MagnifierStyle Default;
-    property public final androidx.compose.foundation.MagnifierStyle TextDefault;
+    method public static androidx.compose.ui.Modifier magnifier(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Density,androidx.compose.ui.geometry.Offset> sourceCenter, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Density,androidx.compose.ui.geometry.Offset> magnifierCenter, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpSize,kotlin.Unit>? onSizeChanged, optional float zoom, optional long size, optional float cornerRadius, optional float elevation, optional boolean clippingEnabled);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @kotlin.jvm.JvmInline public final value class MarqueeAnimationMode {
@@ -260,6 +270,14 @@
     property public final androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.ScrollState,?> Saver;
   }
 
+  public interface SurfaceCoroutineScope extends androidx.compose.foundation.SurfaceScope kotlinx.coroutines.CoroutineScope {
+  }
+
+  public interface SurfaceScope {
+    method public void onChanged(android.view.Surface, kotlin.jvm.functions.Function3<? super android.view.Surface,? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> onChanged);
+    method public void onDestroyed(android.view.Surface, kotlin.jvm.functions.Function1<? super android.view.Surface,kotlin.Unit> onDestroyed);
+  }
+
   public final class SystemGestureExclusionKt {
     method public static androidx.compose.ui.Modifier systemGestureExclusion(androidx.compose.ui.Modifier);
     method public static androidx.compose.ui.Modifier systemGestureExclusion(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.LayoutCoordinates,androidx.compose.ui.geometry.Rect> exclusion);
@@ -416,8 +434,8 @@
   }
 
   public final class ScrollableKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier scrollable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.ScrollableState state, androidx.compose.foundation.gestures.Orientation orientation, androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.gestures.BringIntoViewSpec bringIntoViewSpec);
-    method public static androidx.compose.ui.Modifier scrollable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.ScrollableState state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier scrollable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.ScrollableState state, androidx.compose.foundation.gestures.Orientation orientation, androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.gestures.BringIntoViewSpec bringIntoViewSpec);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier scrollable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.ScrollableState state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface ScrollableState {
@@ -494,7 +512,7 @@
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public final class SnapFlingBehavior implements androidx.compose.foundation.gestures.FlingBehavior {
-    ctor public SnapFlingBehavior(androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider snapLayoutInfoProvider, androidx.compose.animation.core.AnimationSpec<java.lang.Float> lowVelocityAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> highVelocityAnimationSpec, androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.ui.unit.Density density, optional float shortSnapVelocityThreshold);
+    ctor public SnapFlingBehavior(androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider snapLayoutInfoProvider, androidx.compose.animation.core.AnimationSpec<java.lang.Float> lowVelocityAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> highVelocityAnimationSpec, androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, float shortSnapVelocityThreshold);
     method public suspend Object? performFling(androidx.compose.foundation.gestures.ScrollScope, float initialVelocity, kotlin.coroutines.Continuation<? super java.lang.Float>);
     method public suspend Object? performFling(androidx.compose.foundation.gestures.ScrollScope, float initialVelocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onSettlingDistanceUpdated, kotlin.coroutines.Continuation<? super java.lang.Float>);
   }
@@ -504,13 +522,12 @@
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public interface SnapLayoutInfoProvider {
-    method public float calculateApproachOffset(androidx.compose.ui.unit.Density, float initialVelocity);
-    method public float calculateSnapStepSize(androidx.compose.ui.unit.Density);
-    method public float calculateSnappingOffset(androidx.compose.ui.unit.Density, float currentVelocity);
+    method public float calculateApproachOffset(float initialVelocity);
+    method public float calculateSnappingOffset(float currentVelocity);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public fun interface SnapPositionInLayout {
-    method public int position(androidx.compose.ui.unit.Density, int layoutSize, int itemSize, int itemIndex);
+    method public int position(int layoutSize, int itemSize, int itemIndex);
     field public static final androidx.compose.foundation.gestures.snapping.SnapPositionInLayout.Companion Companion;
   }
 
@@ -1226,7 +1243,7 @@
 package androidx.compose.foundation.selection {
 
   public final class SelectableGroupKt {
-    method public static androidx.compose.ui.Modifier selectableGroup(androidx.compose.ui.Modifier);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier selectableGroup(androidx.compose.ui.Modifier);
   }
 
   public final class SelectableKt {
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 1ab4f695..5a3ef8a 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -2,13 +2,13 @@
 package androidx.compose.foundation {
 
   public final class BackgroundKt {
-    method public static androidx.compose.ui.Modifier background(androidx.compose.ui.Modifier, androidx.compose.ui.graphics.Brush brush, optional androidx.compose.ui.graphics.Shape shape, optional @FloatRange(from=0.0, to=1.0) float alpha);
-    method public static androidx.compose.ui.Modifier background(androidx.compose.ui.Modifier, long color, optional androidx.compose.ui.graphics.Shape shape);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier background(androidx.compose.ui.Modifier, androidx.compose.ui.graphics.Brush brush, optional androidx.compose.ui.graphics.Shape shape, optional @FloatRange(from=0.0, to=1.0) float alpha);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier background(androidx.compose.ui.Modifier, long color, optional androidx.compose.ui.graphics.Shape shape);
   }
 
   public final class BasicMarqueeKt {
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.MarqueeSpacing MarqueeSpacing(float spacing);
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier basicMarquee(androidx.compose.ui.Modifier, optional int iterations, optional int animationMode, optional int delayMillis, optional int initialDelayMillis, optional androidx.compose.foundation.MarqueeSpacing spacing, optional float velocity);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier basicMarquee(androidx.compose.ui.Modifier, optional int iterations, optional int animationMode, optional int delayMillis, optional int initialDelayMillis, optional androidx.compose.foundation.MarqueeSpacing spacing, optional float velocity);
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static int getDefaultMarqueeDelayMillis();
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static int getDefaultMarqueeIterations();
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.MarqueeSpacing getDefaultMarqueeSpacing();
@@ -28,7 +28,7 @@
 
   public final class BasicTooltipKt {
     method @androidx.compose.runtime.Composable public static void BasicTooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.foundation.BasicTooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method public static androidx.compose.foundation.BasicTooltipState BasicTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
+    method @androidx.compose.runtime.Stable public static androidx.compose.foundation.BasicTooltipState BasicTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
     method @androidx.compose.runtime.Composable public static androidx.compose.foundation.BasicTooltipState rememberBasicTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
   }
 
@@ -47,9 +47,9 @@
   }
 
   public final class BorderKt {
-    method public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, androidx.compose.foundation.BorderStroke border, optional androidx.compose.ui.graphics.Shape shape);
-    method public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, float width, androidx.compose.ui.graphics.Brush brush, androidx.compose.ui.graphics.Shape shape);
-    method public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, float width, long color, optional androidx.compose.ui.graphics.Shape shape);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, androidx.compose.foundation.BorderStroke border, optional androidx.compose.ui.graphics.Shape shape);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, float width, androidx.compose.ui.graphics.Brush brush, androidx.compose.ui.graphics.Shape shape);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, float width, long color, optional androidx.compose.ui.graphics.Shape shape);
   }
 
   @androidx.compose.runtime.Immutable public final class BorderStroke {
@@ -83,7 +83,7 @@
   }
 
   public final class ClipScrollableContainerKt {
-    method public static androidx.compose.ui.Modifier clipScrollableContainer(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.Orientation orientation);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier clipScrollableContainer(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.Orientation orientation);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface CombinedClickableNode extends androidx.compose.ui.node.PointerInputModifierNode {
@@ -103,7 +103,7 @@
   }
 
   public final class FocusableKt {
-    method public static androidx.compose.ui.Modifier focusGroup(androidx.compose.ui.Modifier);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier focusGroup(androidx.compose.ui.Modifier);
     method public static androidx.compose.ui.Modifier focusable(androidx.compose.ui.Modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
@@ -111,6 +111,30 @@
     method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier onFocusedBoundsChanged(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.LayoutCoordinates,kotlin.Unit> onPositioned);
   }
 
+  public final class GraphicsSurfaceKt {
+    method @androidx.compose.runtime.Composable public static void EmbeddedGraphicsSurface(optional androidx.compose.ui.Modifier modifier, optional boolean isOpaque, optional long surfaceSize, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.GraphicsSurfaceScope,kotlin.Unit> onInit);
+    method @androidx.compose.runtime.Composable public static void GraphicsSurface(optional androidx.compose.ui.Modifier modifier, optional boolean isOpaque, optional int zOrder, optional long surfaceSize, optional boolean isSecure, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.GraphicsSurfaceScope,kotlin.Unit> onInit);
+  }
+
+  public interface GraphicsSurfaceScope {
+    method public void onSurface(kotlin.jvm.functions.Function5<? super androidx.compose.foundation.SurfaceCoroutineScope,? super android.view.Surface,? super java.lang.Integer,? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onSurface);
+  }
+
+  @kotlin.jvm.JvmInline public final value class GraphicsSurfaceZOrder {
+    method public int getZOrder();
+    property public final int zOrder;
+    field public static final androidx.compose.foundation.GraphicsSurfaceZOrder.Companion Companion;
+  }
+
+  public static final class GraphicsSurfaceZOrder.Companion {
+    method public int getBehind();
+    method public int getMediaOverlay();
+    method public int getOnTop();
+    property public final int Behind;
+    property public final int MediaOverlay;
+    property public final int OnTop;
+  }
+
   public final class HoverableKt {
     method public static androidx.compose.ui.Modifier hoverable(androidx.compose.ui.Modifier, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional boolean enabled);
   }
@@ -140,21 +164,7 @@
   }
 
   public final class MagnifierKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier magnifier(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Density,androidx.compose.ui.geometry.Offset> sourceCenter, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Density,androidx.compose.ui.geometry.Offset> magnifierCenter, optional float zoom, optional androidx.compose.foundation.MagnifierStyle style, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpSize,kotlin.Unit>? onSizeChanged);
-  }
-
-  @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public final class MagnifierStyle {
-    ctor @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public MagnifierStyle(optional long size, optional float cornerRadius, optional float elevation, optional boolean clippingEnabled, optional boolean fishEyeEnabled);
-    method public boolean isSupported();
-    property public final boolean isSupported;
-    field public static final androidx.compose.foundation.MagnifierStyle.Companion Companion;
-  }
-
-  public static final class MagnifierStyle.Companion {
-    method public androidx.compose.foundation.MagnifierStyle getDefault();
-    method public androidx.compose.foundation.MagnifierStyle getTextDefault();
-    property public final androidx.compose.foundation.MagnifierStyle Default;
-    property public final androidx.compose.foundation.MagnifierStyle TextDefault;
+    method public static androidx.compose.ui.Modifier magnifier(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Density,androidx.compose.ui.geometry.Offset> sourceCenter, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Density,androidx.compose.ui.geometry.Offset> magnifierCenter, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.DpSize,kotlin.Unit>? onSizeChanged, optional float zoom, optional long size, optional float cornerRadius, optional float elevation, optional boolean clippingEnabled);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @kotlin.jvm.JvmInline public final value class MarqueeAnimationMode {
@@ -262,6 +272,14 @@
     property public final androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.ScrollState,?> Saver;
   }
 
+  public interface SurfaceCoroutineScope extends androidx.compose.foundation.SurfaceScope kotlinx.coroutines.CoroutineScope {
+  }
+
+  public interface SurfaceScope {
+    method public void onChanged(android.view.Surface, kotlin.jvm.functions.Function3<? super android.view.Surface,? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> onChanged);
+    method public void onDestroyed(android.view.Surface, kotlin.jvm.functions.Function1<? super android.view.Surface,kotlin.Unit> onDestroyed);
+  }
+
   public final class SystemGestureExclusionKt {
     method public static androidx.compose.ui.Modifier systemGestureExclusion(androidx.compose.ui.Modifier);
     method public static androidx.compose.ui.Modifier systemGestureExclusion(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.layout.LayoutCoordinates,androidx.compose.ui.geometry.Rect> exclusion);
@@ -418,8 +436,8 @@
   }
 
   public final class ScrollableKt {
-    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier scrollable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.ScrollableState state, androidx.compose.foundation.gestures.Orientation orientation, androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.gestures.BringIntoViewSpec bringIntoViewSpec);
-    method public static androidx.compose.ui.Modifier scrollable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.ScrollableState state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+    method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier scrollable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.ScrollableState state, androidx.compose.foundation.gestures.Orientation orientation, androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.gestures.BringIntoViewSpec bringIntoViewSpec);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier scrollable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.ScrollableState state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
   }
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface ScrollableState {
@@ -496,7 +514,7 @@
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public final class SnapFlingBehavior implements androidx.compose.foundation.gestures.FlingBehavior {
-    ctor public SnapFlingBehavior(androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider snapLayoutInfoProvider, androidx.compose.animation.core.AnimationSpec<java.lang.Float> lowVelocityAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> highVelocityAnimationSpec, androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, androidx.compose.ui.unit.Density density, optional float shortSnapVelocityThreshold);
+    ctor public SnapFlingBehavior(androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider snapLayoutInfoProvider, androidx.compose.animation.core.AnimationSpec<java.lang.Float> lowVelocityAnimationSpec, androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> highVelocityAnimationSpec, androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, float shortSnapVelocityThreshold);
     method public suspend Object? performFling(androidx.compose.foundation.gestures.ScrollScope, float initialVelocity, kotlin.coroutines.Continuation<? super java.lang.Float>);
     method public suspend Object? performFling(androidx.compose.foundation.gestures.ScrollScope, float initialVelocity, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onSettlingDistanceUpdated, kotlin.coroutines.Continuation<? super java.lang.Float>);
   }
@@ -506,13 +524,12 @@
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public interface SnapLayoutInfoProvider {
-    method public float calculateApproachOffset(androidx.compose.ui.unit.Density, float initialVelocity);
-    method public float calculateSnapStepSize(androidx.compose.ui.unit.Density);
-    method public float calculateSnappingOffset(androidx.compose.ui.unit.Density, float currentVelocity);
+    method public float calculateApproachOffset(float initialVelocity);
+    method public float calculateSnappingOffset(float currentVelocity);
   }
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public fun interface SnapPositionInLayout {
-    method public int position(androidx.compose.ui.unit.Density, int layoutSize, int itemSize, int itemIndex);
+    method public int position(int layoutSize, int itemSize, int itemIndex);
     field public static final androidx.compose.foundation.gestures.snapping.SnapPositionInLayout.Companion Companion;
   }
 
@@ -1228,7 +1245,7 @@
 package androidx.compose.foundation.selection {
 
   public final class SelectableGroupKt {
-    method public static androidx.compose.ui.Modifier selectableGroup(androidx.compose.ui.Modifier);
+    method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier selectableGroup(androidx.compose.ui.Modifier);
   }
 
   public final class SelectableKt {
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
index 971cc2b..4a07721 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/FoundationDemos.kt
@@ -51,6 +51,12 @@
     ComposableDemo("Draggable, Scrollable, Zoomable, Focusable") { HighLevelGesturesDemo() }
 )
 
+private val NestedScrollDemos = listOf(
+    ComposableDemo("Nested Scroll") { NestedScrollDemo() },
+    ComposableDemo("Nested Scroll Connection") { NestedScrollConnectionSample() },
+    ComposableDemo("Nested Scroll Simple Column") { SimpleColumnNestedScrollSample() },
+)
+
 val FoundationDemos = DemoCategory(
     "Foundation",
     listOf(
@@ -60,14 +66,14 @@
         ComposableDemo("Vertical scroll") { VerticalScrollExample() },
         ComposableDemo("Controlled Scrollable Row") { ControlledScrollableRowSample() },
         ComposableDemo("Draw Modifiers") { DrawModifiersDemo() },
+        ComposableDemo("Graphics Surfaces") { GraphicsSurfaceDemo() },
         DemoCategory("Lazy lists", LazyListDemos),
         DemoCategory("Snapping", SnappingDemos),
         DemoCategory("Pagers", PagerDemos),
         ComposableDemo("Simple InteractionSource") { SimpleInteractionSourceSample() },
         ComposableDemo("Flow InteractionSource") { InteractionSourceFlowSample() },
         DemoCategory("Suspending Gesture Detectors", CoroutineGestureDemos),
-        ComposableDemo("Nested Scroll") { NestedScrollDemo() },
-        ComposableDemo("Nested Scroll Connection") { NestedScrollConnectionSample() },
+        DemoCategory("Nested Scroll", NestedScrollDemos),
         DemoCategory("Relocation Demos", RelocationDemos),
         DemoCategory("Focus Demos", FocusDemos),
         DemoCategory("Magnifier Demos", MagnifierDemos),
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/GraphicsSurfaceDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/GraphicsSurfaceDemo.kt
new file mode 100644
index 0000000..4cc8c77
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/GraphicsSurfaceDemo.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019 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.demos
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.samples.EmbeddedGraphicsSurfaceColors
+import androidx.compose.foundation.samples.GraphicsSurfaceColors
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun GraphicsSurfaceDemo() {
+    Column(Modifier.verticalScroll(rememberScrollState())) {
+        Text("GraphicsSurface:")
+        GraphicsSurfaceColors()
+        Spacer(Modifier.height(50.dp))
+        Text("EmbeddedGraphicsSurface:")
+        EmbeddedGraphicsSurfaceColors()
+    }
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/MagnifierDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/MagnifierDemos.kt
index 0121df1..3a98a7e 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/MagnifierDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/MagnifierDemos.kt
@@ -16,12 +16,11 @@
 
 package androidx.compose.foundation.demos
 
+import android.os.Build
 import androidx.compose.animation.animateColor
 import androidx.compose.animation.core.infiniteRepeatable
 import androidx.compose.animation.core.rememberInfiniteTransition
 import androidx.compose.animation.core.tween
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.MagnifierStyle
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
@@ -59,14 +58,7 @@
     ComposableDemo("Multitouch Custom Magnifier") { MultitouchCustomMagnifierDemo() },
 )
 
-@OptIn(ExperimentalFoundationApi::class)
-private val DemoMagnifierStyle = MagnifierStyle(
-    size = DpSize(100.dp, 100.dp),
-    cornerRadius = 50.dp,
-)
-
 @Preview
-@OptIn(ExperimentalFoundationApi::class)
 @Composable
 fun MultitouchCustomMagnifierDemo() {
     // Track the offset for every pointer ID that is currently "down".
@@ -86,7 +78,7 @@
             style = TextStyle(textAlign = TextAlign.Center),
             modifier = Modifier.fillMaxWidth()
         )
-        if (!DemoMagnifierStyle.isSupported) {
+        if (Build.VERSION.SDK_INT < 28) {
             Text(
                 "Magnifier not supported on this platform.",
                 color = Color.Red,
@@ -150,7 +142,8 @@
                                     ?: Offset.Zero
                             },
                             zoom = 3f,
-                            style = DemoMagnifierStyle
+                            size = DpSize(100.dp, 100.dp),
+                            cornerRadius = 50.dp,
                         )
                     )
                 }
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/NestedScrollDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/NestedScrollDemos.kt
index fe39345..f9900e2 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/NestedScrollDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/NestedScrollDemos.kt
@@ -18,6 +18,8 @@
 
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
@@ -106,6 +108,7 @@
                 contentAlignment = Alignment.Center
             ) {
                 Text(
+                    modifier = Modifier.focusable(),
                     text = "$outerOuterIndex : $outerIndex : $innerIndex",
                     fontSize = 24.sp
                 )
@@ -145,9 +148,55 @@
                 items(100) { index ->
                     Text("I'm item $index", modifier = Modifier
                         .fillMaxWidth()
+                        .focusable()
                         .padding(16.dp))
                 }
             }
         }
     }
 }
+
+@Composable
+fun SimpleColumnNestedScrollSample() {
+    val scrollState = rememberScrollState()
+
+    Column(
+        Modifier
+            .fillMaxWidth()
+            .background(Color.Red)
+            .verticalScroll(scrollState),
+        verticalArrangement = Arrangement.spacedBy(20.dp)
+    ) {
+        Text(
+            modifier = Modifier.fillMaxWidth(),
+            text = "Outer Scrollable Column"
+        )
+
+        for (i in 0 until 4) {
+            SimpleColumn("Inner Scrollable Column: $i")
+        }
+    }
+}
+
+@Composable
+fun SimpleColumn(label: String) {
+    Column(
+        Modifier
+            .fillMaxWidth()
+            .height(200.dp)
+            .background(Color.Green)
+            .verticalScroll(rememberScrollState())
+    ) {
+        Text(
+            modifier = Modifier.fillMaxWidth(),
+            text = "$label INNER, scrollable only"
+        )
+
+        for (i in 0 until 20) {
+            Text(
+                modifier = Modifier.fillMaxWidth().focusable(),
+                text = "Text $i",
+            )
+        }
+    }
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/LazyGridSnappingDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/LazyGridSnappingDemos.kt
index 86151d9..3f84793 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/LazyGridSnappingDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/LazyGridSnappingDemos.kt
@@ -42,12 +42,16 @@
 import androidx.compose.ui.unit.sp
 
 val LazyGridSnappingDemos = listOf(
-    ComposableDemo("Single Page - Same Size Pages") { GridSinglePageSnapping() },
+    ComposableDemo("Single Item - Same Size Items") { GridSingleItemSnapping() },
 )
 
+/**
+ * Snapping happens to the next item and items have the same size. We use the top line in the grid
+ * as a reference point.
+ */
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-private fun GridSinglePageSnapping() {
+private fun GridSingleItemSnapping() {
     val lazyGridState = rememberLazyGridState()
     val snappingLayout = remember(lazyGridState) { SnapLayoutInfoProvider(lazyGridState) }
     val flingBehavior = rememberSnapFlingBehavior(snapLayoutInfoProvider = snappingLayout)
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/LazyListSnappingDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/LazyListSnappingDemos.kt
index 962eaf7..b35a1e1 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/LazyListSnappingDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/LazyListSnappingDemos.kt
@@ -27,23 +27,26 @@
 import androidx.compose.integration.demos.common.ComposableDemo
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
-import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastSumBy
 
 val LazyListSnappingDemos = listOf(
-    ComposableDemo("Single Page - Same Size Pages") { SamePageSizeDemo() },
-    ComposableDemo("Single Page - Multi-Size Pages") { MultiSizePageDemo() },
-    ComposableDemo("Single Page - Large Pages") { LargePageSizeDemo() },
-    ComposableDemo("Single Page - List with Content padding") { DifferentContentPaddingDemo() },
-    ComposableDemo("Multi Page - Animation Based Offset") { MultiPageSnappingDemo() },
-    ComposableDemo("Multi Page - View Port Based Offset") { ViewPortBasedSnappingDemo() },
+    ComposableDemo("Single Item - Same Size Items") { SameItemSizeDemo() },
+    ComposableDemo("Single Item - Different Size Item") { DifferentItemSizeDemo() },
+    ComposableDemo("Single Item - Large Items") { LargeItemSizeDemo() },
+    ComposableDemo("Single Item - List with Content padding") { DifferentContentPaddingDemo() },
+    ComposableDemo("Multi Item - Decayed Snapping") { DecayedSnappingDemo() },
+    ComposableDemo("Multi Item - View Port Based Offset") { ViewPortBasedSnappingDemo() },
 )
 
+/**
+ * Snapping happens to the next item and items have the same size
+ */
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-private fun SamePageSizeDemo() {
+private fun SameItemSizeDemo() {
     val lazyListState = rememberLazyListState()
-    val layoutInfoProvider = rememberNextPageSnappingLayoutInfoProvider(lazyListState)
+    val layoutInfoProvider = rememberNextItemSnappingLayoutInfoProvider(lazyListState)
     val flingBehavior = rememberSnapFlingBehavior(layoutInfoProvider)
 
     SnappingDemoMainLayout(
@@ -54,11 +57,14 @@
     }
 }
 
+/**
+ * Snapping happens to the next item and items have the different sizes
+ */
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-private fun MultiSizePageDemo() {
+private fun DifferentItemSizeDemo() {
     val lazyListState = rememberLazyListState()
-    val layoutInfoProvider = rememberNextPageSnappingLayoutInfoProvider(lazyListState)
+    val layoutInfoProvider = rememberNextItemSnappingLayoutInfoProvider(lazyListState)
     val flingBehavior = rememberSnapFlingBehavior(layoutInfoProvider)
 
     SnappingDemoMainLayout(lazyListState = lazyListState, flingBehavior = flingBehavior) {
@@ -70,11 +76,14 @@
     }
 }
 
+/**
+ * Snapping happens to the next item and items are larger than the view port
+ */
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-private fun LargePageSizeDemo() {
+private fun LargeItemSizeDemo() {
     val lazyListState = rememberLazyListState()
-    val layoutInfoProvider = rememberNextPageSnappingLayoutInfoProvider(lazyListState)
+    val layoutInfoProvider = rememberNextItemSnappingLayoutInfoProvider(lazyListState)
     val flingBehavior = rememberSnapFlingBehavior(layoutInfoProvider)
 
     SnappingDemoMainLayout(lazyListState = lazyListState, flingBehavior = flingBehavior) {
@@ -86,6 +95,9 @@
     }
 }
 
+/**
+ * Snapping happens to the next item and list has content paddings
+ */
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun DifferentContentPaddingDemo() {
@@ -103,9 +115,12 @@
     }
 }
 
+/**
+ * Snapping happens after a decay animation and items have the same size
+ */
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-private fun MultiPageSnappingDemo() {
+private fun DecayedSnappingDemo() {
     val lazyListState = rememberLazyListState()
     val flingBehavior = rememberSnapFlingBehavior(lazyListState)
     SnappingDemoMainLayout(lazyListState = lazyListState, flingBehavior = flingBehavior) {
@@ -113,6 +128,9 @@
     }
 }
 
+/**
+ * Snapping happens to at max one view port item's worth distance.
+ */
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun ViewPortBasedSnappingDemo() {
@@ -127,13 +145,13 @@
 
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-private fun rememberNextPageSnappingLayoutInfoProvider(
+private fun rememberNextItemSnappingLayoutInfoProvider(
     state: LazyListState
 ): SnapLayoutInfoProvider {
     return remember(state) {
         val basedSnappingLayoutInfoProvider = SnapLayoutInfoProvider(lazyListState = state)
         object : SnapLayoutInfoProvider by basedSnappingLayoutInfoProvider {
-            override fun Density.calculateApproachOffset(initialVelocity: Float): Float {
+            override fun calculateApproachOffset(initialVelocity: Float): Float {
                 return 0f
             }
         }
@@ -150,6 +168,10 @@
         ViewPortBasedSnappingLayoutInfoProvider(
             SnapLayoutInfoProvider(lazyListState = state),
             decayAnimationSpec,
+            {
+                val visibleItemsSum = state.layoutInfo.visibleItemsInfo.fastSumBy { it.size }
+                visibleItemsSum / state.layoutInfo.visibleItemsInfo.size.toFloat()
+            }
         ) { state.layoutInfo.viewportSize.width.toFloat() }
     }
 }
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/NonItemBasedSnapping.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/NonItemBasedSnapping.kt
new file mode 100644
index 0000000..f982d18
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/NonItemBasedSnapping.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.demos.snapping
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.rememberScrollableState
+import androidx.compose.foundation.gestures.scrollable
+import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
+import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+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.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import kotlin.math.abs
+
+/**
+ * A provider that doesn't use the concept of items for snapping.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+class NonItemBasedSnappingLayoutInfoProvider(
+    private val currentOffset: Int,
+    layoutSize: Int,
+    thumbSize: Int
+) : SnapLayoutInfoProvider {
+
+    // start, middle, end of the layout
+    private val offsetList = listOf(0, layoutSize / 2 - thumbSize / 2, (layoutSize - thumbSize))
+
+    // do not approach, our snapping positions are discrete.
+    override fun calculateApproachOffset(initialVelocity: Float): Float = 0f
+
+    override fun calculateSnappingOffset(currentVelocity: Float): Float {
+        val targetOffset = if (currentVelocity == 0.0f) {
+            // snap to closest offset
+            var closestOffset = 0
+            var prevMinAbs = Int.MAX_VALUE
+            offsetList.forEach {
+                val absDistance = abs(currentOffset - it)
+                if (absDistance < prevMinAbs) {
+                    prevMinAbs = absDistance
+                    closestOffset = it
+                }
+            }
+            (closestOffset).toFloat()
+        } else if (currentVelocity > 0) {
+            // snap to the next offset
+            val offset = offsetList.firstOrNull { it > currentOffset }
+            (offset ?: 0).toFloat() // if offset is found, move there, if not, don't move
+        } else {
+            // snap to the previous offset
+            val offset = offsetList.reversed().firstOrNull { it < currentOffset }
+            (offset ?: 0).toFloat() // if offset is found, move there, if not, don't move
+        }
+        return targetOffset - currentOffset // distance that needs to be consumed to reach target
+    }
+}
+
+private val ThumbSize = 60.dp
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+fun NonItemBasedLayout() {
+    var thumbOffset by remember { mutableStateOf(IntOffset.Zero) }
+    var layoutSize by remember { mutableIntStateOf(0) }
+
+    val thumbSize = with(LocalDensity.current) { ThumbSize.roundToPx() }
+    val maxPosition = with(LocalDensity.current) {
+        layoutSize - ThumbSize.roundToPx()
+    }
+
+    val snapLayoutInfoProvider = remember(thumbOffset, layoutSize, thumbSize) {
+        NonItemBasedSnappingLayoutInfoProvider(thumbOffset.x, layoutSize, thumbSize)
+    }
+
+    val fling = rememberSnapFlingBehavior(snapLayoutInfoProvider = snapLayoutInfoProvider)
+    val scrollableState = rememberScrollableState {
+        val previousPosition = thumbOffset.x
+        val newPosition = (previousPosition + it).coerceIn(0.0f, maxPosition.toFloat()).toInt()
+        thumbOffset = thumbOffset.copy(x = newPosition)
+        it // need to return correct consumption
+    }
+    Box(
+        modifier = Modifier
+            .requiredHeight(ThumbSize)
+            .fillMaxWidth()
+            .background(Color.LightGray)
+            .scrollable(
+                scrollableState,
+                orientation = Orientation.Horizontal,
+                flingBehavior = fling
+            )
+            .onSizeChanged {
+                layoutSize = it.width
+            }
+    ) {
+        Box(modifier = Modifier
+            .offset { thumbOffset }
+            .requiredSize(ThumbSize)
+            .background(Color.Red))
+    }
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/RowSnapLayoutInfoProvider.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/RowSnapLayoutInfoProvider.kt
index f2e5bae..375e80a 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/RowSnapLayoutInfoProvider.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/RowSnapLayoutInfoProvider.kt
@@ -19,47 +19,44 @@
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
-import androidx.compose.ui.unit.Density
 import kotlin.math.abs
 import kotlin.math.ceil
 import kotlin.math.floor
 import kotlin.math.roundToInt
 import kotlin.math.sign
 
+@Suppress("PrimitiveInLambda")
 @OptIn(ExperimentalFoundationApi::class)
 fun SnapLayoutInfoProvider(
     scrollState: ScrollState,
-    itemSize: Density.() -> Float,
-    layoutSize: Density.() -> Float
+    itemSize: () -> Float,
+    layoutSize: () -> Float
 ) = object : SnapLayoutInfoProvider {
 
-    fun Density.nextFullItemCenter(layoutCenter: Float): Float {
+    fun nextFullItemCenter(layoutCenter: Float): Float {
         val intItemSize = itemSize().roundToInt()
-        return floor((layoutCenter + calculateSnapStepSize()) / itemSize().roundToInt()) *
+        return floor((layoutCenter + itemSize()) / itemSize().roundToInt()) *
             intItemSize
     }
 
-    fun Density.previousFullItemCenter(layoutCenter: Float): Float {
+    fun previousFullItemCenter(layoutCenter: Float): Float {
         val intItemSize = itemSize().roundToInt()
-        return ceil((layoutCenter - calculateSnapStepSize()) / itemSize().roundToInt()) *
+        return ceil((layoutCenter - itemSize()) / itemSize().roundToInt()) *
             intItemSize
     }
 
-    override fun Density.calculateSnappingOffset(currentVelocity: Float): Float {
-        val layoutCenter = layoutSize() / 2f + scrollState.value + calculateSnapStepSize() / 2f
+    override fun calculateSnappingOffset(currentVelocity: Float): Float {
+        val layoutCenter = layoutSize() / 2f + scrollState.value + itemSize() / 2f
         val lowerBound = nextFullItemCenter(layoutCenter) - layoutCenter
         val upperBound = previousFullItemCenter(layoutCenter) - layoutCenter
 
         return calculateFinalOffset(currentVelocity, upperBound, lowerBound)
     }
 
-    override fun Density.calculateSnapStepSize(): Float {
-        return itemSize()
-    }
-
-    override fun Density.calculateApproachOffset(initialVelocity: Float): Float = 0f
+    override fun calculateApproachOffset(initialVelocity: Float): Float = 0f
 }
 
+@Suppress("PrimitiveInLambda")
 internal fun calculateFinalOffset(velocity: Float, lowerBound: Float, upperBound: Float): Float {
 
     fun Float.isValidDistance(): Boolean {
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/RowSnappingDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/RowSnappingDemos.kt
index e28f6a6..aace1fa 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/RowSnappingDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/RowSnappingDemos.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.demos.snapping
 
 import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.animation.core.calculateTargetValue
 import androidx.compose.animation.rememberSplineBasedDecay
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.ScrollState
@@ -44,17 +45,21 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import kotlin.math.absoluteValue
+import kotlin.math.sign
 
 val RowSnappingDemos = listOf(
-    ComposableDemo("Single Page - Same Size Pages") { SinglePageSnapping() },
-    ComposableDemo("Multi Page - Animation Based Offset") { MultiPageSnappingDemo() },
-    ComposableDemo("Multi Page - View Port Based Offset") { ViewPortBasedSnappingDemo() },
+    ComposableDemo("Single Item - Same Size Items") { SinglePageSnapping() },
+    ComposableDemo("Multi Item - Decayed Snapping") { DecayedSnappingDemo() },
+    ComposableDemo("Multi Item - View Port Based Offset") { ViewPortBasedSnappingDemo() },
 )
 
+/**
+ * Snapping happens to the next item and items have the same size
+ */
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun SinglePageSnapping() {
@@ -67,30 +72,37 @@
     RowSnappingMainLayout(snapFlingBehavior, scrollState) { layoutSizeState.value = it }
 }
 
+/**
+ * Snapping happens after a decay animation. Items have the same size.
+ */
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-private fun MultiPageSnappingDemo() {
+private fun DecayedSnappingDemo() {
     val scrollState = rememberScrollState()
     val layoutSizeState = remember { mutableStateOf(IntSize.Zero) }
-    val layoutInfoProvider = rememberMultiPageRowSnapLayoutInfoProvider(scrollState) {
+    val layoutInfoProvider = rememberDecayedSnappingLayoutInfoProvider(scrollState) {
         layoutSizeState.value.width.toFloat()
     }
     val snapFlingBehavior = rememberSnapFlingBehavior(snapLayoutInfoProvider = layoutInfoProvider)
     RowSnappingMainLayout(snapFlingBehavior, scrollState) { layoutSizeState.value = it }
 }
 
+/**
+ * Snapping happens to at max one view port item's worth distance.
+ */
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun ViewPortBasedSnappingDemo() {
     val scrollState = rememberScrollState()
     val layoutSizeState = remember { mutableStateOf(IntSize.Zero) }
-    val layoutInfoProvider = rememberViewPortRowSnapLayoutInfoProvider(scrollState) {
+    val layoutInfoProvider = rememberViewPortSnapLayoutInfoProvider(scrollState) {
         layoutSizeState.value.width.toFloat()
     }
     val snapFlingBehavior = rememberSnapFlingBehavior(snapLayoutInfoProvider = layoutInfoProvider)
     RowSnappingMainLayout(snapFlingBehavior, scrollState) { layoutSizeState.value = it }
 }
 
+@Suppress("PrimitiveInLambda")
 @Composable
 private fun RowSnappingMainLayout(
     snapFlingBehavior: FlingBehavior,
@@ -137,57 +149,97 @@
     }
 }
 
+@Suppress("PrimitiveInLambda")
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
 private fun rememberRowSnapLayoutInfoProvider(
     scrollState: ScrollState,
-    layoutSize: Density.() -> Float
+    layoutSize: () -> Float
 ): SnapLayoutInfoProvider {
+    val density = LocalDensity.current
     return remember(scrollState, layoutSize) {
         SnapLayoutInfoProvider(
             scrollState = scrollState,
-            itemSize = { RowItemSize.toPx() },
+            itemSize = { with(density) { RowItemSize.toPx() } },
             layoutSize = layoutSize
         )
     }
 }
 
+@Suppress("PrimitiveInLambda")
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-private fun rememberMultiPageRowSnapLayoutInfoProvider(
+private fun rememberDecayedSnappingLayoutInfoProvider(
     scrollState: ScrollState,
-    layoutSize: Density.() -> Float
+    layoutSize: () -> Float
 ): SnapLayoutInfoProvider {
     val animation: DecayAnimationSpec<Float> = rememberSplineBasedDecay()
+    val density = LocalDensity.current
+    val scrollStateLayoutInfoProvider = SnapLayoutInfoProvider(
+        scrollState = scrollState,
+        itemSize = { with(density) { RowItemSize.toPx() } },
+        layoutSize = layoutSize
+    )
     return remember(scrollState, layoutSize) {
-        MultiPageSnappingLayoutInfoProvider(
-            SnapLayoutInfoProvider(
-                scrollState = scrollState,
-                itemSize = { RowItemSize.toPx() },
-                layoutSize = layoutSize
-            ),
-            animation
+        DecayedSnappingLayoutInfoProvider(
+            scrollStateLayoutInfoProvider,
+            animation,
+        ) { with(density) { RowItemSize.toPx() } }
+    }
+}
+
+@Suppress("PrimitiveInLambda")
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+private fun rememberViewPortSnapLayoutInfoProvider(
+    scrollState: ScrollState,
+    layoutSize: () -> Float
+): SnapLayoutInfoProvider {
+    val density = LocalDensity.current
+    val decayAnimationSpec: DecayAnimationSpec<Float> = rememberSplineBasedDecay()
+    val baseSnapLayoutInfoProvider = rememberScrollStateLayoutInfoProvider(
+        scrollState = scrollState,
+        layoutSize = layoutSize
+    )
+
+    return remember(baseSnapLayoutInfoProvider, density, layoutSize) {
+        ViewPortBasedSnappingLayoutInfoProvider(
+            baseSnapLayoutInfoProvider,
+            decayAnimationSpec,
+            viewPortStep = layoutSize,
+            itemSize = { with(density) { RowItemSize.toPx() } }
         )
     }
 }
 
+@Suppress("PrimitiveInLambda")
+@OptIn(ExperimentalFoundationApi::class)
+internal class DecayedSnappingLayoutInfoProvider(
+    private val baseSnapLayoutInfoProvider: SnapLayoutInfoProvider,
+    private val decayAnimationSpec: DecayAnimationSpec<Float>,
+    private val itemSize: () -> Float
+) : SnapLayoutInfoProvider by baseSnapLayoutInfoProvider {
+    override fun calculateApproachOffset(initialVelocity: Float): Float {
+        val offset = decayAnimationSpec.calculateTargetValue(0f, initialVelocity)
+        val finalDecayedOffset = (offset.absoluteValue - itemSize()).coerceAtLeast(0f)
+        return finalDecayedOffset * initialVelocity.sign
+    }
+}
+
+@Suppress("PrimitiveInLambda")
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-private fun rememberViewPortRowSnapLayoutInfoProvider(
+private fun rememberScrollStateLayoutInfoProvider(
     scrollState: ScrollState,
-    layoutSize: Density.() -> Float
+    layoutSize: () -> Float
 ): SnapLayoutInfoProvider {
     val density = LocalDensity.current
-    val decayAnimationSpec: DecayAnimationSpec<Float> = rememberSplineBasedDecay()
-    return remember(scrollState, density, layoutSize) {
-        ViewPortBasedSnappingLayoutInfoProvider(
-            SnapLayoutInfoProvider(
-                scrollState = scrollState,
-                itemSize = { RowItemSize.toPx() },
-                layoutSize = layoutSize
-            ),
-            decayAnimationSpec
-        ) { with(density, layoutSize) }
+    return remember(scrollState, layoutSize, density) {
+        SnapLayoutInfoProvider(
+            scrollState = scrollState,
+            itemSize = { with(density) { RowItemSize.toPx() } },
+            layoutSize = layoutSize
+        )
     }
 }
 
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/SnappingDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/SnappingDemos.kt
index f4e5e58..0605ba4 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/SnappingDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/SnappingDemos.kt
@@ -16,12 +16,8 @@
 
 package androidx.compose.foundation.demos.snapping
 
-import androidx.compose.animation.core.DecayAnimationSpec
-import androidx.compose.animation.core.calculateTargetValue
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.FlingBehavior
-import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.calculateEndPadding
@@ -32,6 +28,7 @@
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.integration.demos.common.ComposableDemo
 import androidx.compose.integration.demos.common.DemoCategory
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
@@ -42,45 +39,21 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.graphics.drawscope.Stroke
-import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
-import kotlin.math.absoluteValue
-import kotlin.math.sign
 
 val SnappingDemos = listOf(
     DemoCategory("Lazy List Snapping", LazyListSnappingDemos),
     DemoCategory("Scrollable Row Snapping", RowSnappingDemos),
     DemoCategory("Lazy Grid Snapping", LazyGridSnappingDemos),
+    ComposableDemo("Non Item based Snapping") {
+        NonItemBasedLayout()
+    },
 )
 
-@OptIn(ExperimentalFoundationApi::class)
-internal class MultiPageSnappingLayoutInfoProvider(
-    private val baseSnapLayoutInfoProvider: SnapLayoutInfoProvider,
-    private val decayAnimationSpec: DecayAnimationSpec<Float>
-) : SnapLayoutInfoProvider by baseSnapLayoutInfoProvider {
-    override fun Density.calculateApproachOffset(initialVelocity: Float): Float {
-        val offset = decayAnimationSpec.calculateTargetValue(0f, initialVelocity)
-        val finalDecayedOffset = (offset.absoluteValue - calculateSnapStepSize()).coerceAtLeast(0f)
-        return finalDecayedOffset * initialVelocity.sign
-    }
-}
-
-@OptIn(ExperimentalFoundationApi::class)
-internal class ViewPortBasedSnappingLayoutInfoProvider(
-    private val baseSnapLayoutInfoProvider: SnapLayoutInfoProvider,
-    private val decayAnimationSpec: DecayAnimationSpec<Float>,
-    private val viewPortStep: () -> Float
-) : SnapLayoutInfoProvider by baseSnapLayoutInfoProvider {
-    override fun Density.calculateApproachOffset(initialVelocity: Float): Float {
-        val offset = decayAnimationSpec.calculateTargetValue(0f, initialVelocity)
-        val viewPortOffset = viewPortStep()
-        return offset.coerceIn(-viewPortOffset, viewPortOffset)
-    }
-}
-
+@Suppress("PrimitiveInLambda")
 @Composable
 internal fun SnappingDemoMainLayout(
     lazyListState: LazyListState,
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/SnappingDemosCommon.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/SnappingDemosCommon.kt
new file mode 100644
index 0000000..34f8703
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/snapping/SnappingDemosCommon.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.demos.snapping
+
+import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.animation.core.calculateTargetValue
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
+import kotlin.math.absoluteValue
+import kotlin.math.sign
+
+@Suppress("PrimitiveInLambda")
+@OptIn(ExperimentalFoundationApi::class)
+internal class ViewPortBasedSnappingLayoutInfoProvider(
+    private val baseSnapLayoutInfoProvider: SnapLayoutInfoProvider,
+    private val decayAnimationSpec: DecayAnimationSpec<Float>,
+    private val viewPortStep: () -> Float,
+    private val itemSize: () -> Float
+) : SnapLayoutInfoProvider by baseSnapLayoutInfoProvider {
+    override fun calculateApproachOffset(initialVelocity: Float): Float {
+        val offset = decayAnimationSpec.calculateTargetValue(0f, initialVelocity)
+        val finalOffset = (offset.absoluteValue - itemSize()).coerceAtLeast(0.0f) * offset.sign
+        val viewPortOffset = viewPortStep()
+        return finalOffset.coerceIn(-viewPortOffset, viewPortOffset)
+    }
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeTextSelectionLazy.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeTextSelectionLazy.kt
new file mode 100644
index 0000000..3e596cc
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeTextSelectionLazy.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.demos.text
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+
+@Preview
+@Composable
+fun TextLazySelectionDemo() {
+    Column {
+        Text(
+            text = "We expect that selection works, regardless of how many times each text" +
+                " goes in or out of composition via scrolling the lazy column.",
+            modifier = Modifier.padding(16.dp),
+        )
+        SelectionContainer {
+            LazyColumn {
+                items(100) {
+                    BasicText(
+                        text = it.toString(),
+                        style = TextStyle(fontSize = fontSize8, textAlign = TextAlign.Center),
+                        modifier = Modifier.fillMaxWidth()
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
index 8b575e1..90304cc 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
@@ -152,6 +152,7 @@
                 ComposableDemo("Text selection") { TextSelectionDemo() },
                 ComposableDemo("Text selection sample") { TextSelectionSample() },
                 ComposableDemo("Overflowed Selection") { TextOverflowedSelectionDemo() },
+                ComposableDemo("LazyColumn Text Selection") { TextLazySelectionDemo() },
             )
         ),
         DemoCategory(
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicSecureTextFieldDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicSecureTextFieldDemos.kt
index c8d01b0..37ecac6 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicSecureTextFieldDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicSecureTextFieldDemos.kt
@@ -30,8 +30,10 @@
 import androidx.compose.foundation.text2.input.TextFieldState
 import androidx.compose.foundation.text2.input.TextObfuscationMode
 import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Button
 import androidx.compose.material.Icon
 import androidx.compose.material.IconToggleButton
+import androidx.compose.material.Text
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Info
 import androidx.compose.material.icons.filled.Warning
@@ -42,6 +44,8 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.unit.dp
@@ -55,6 +59,11 @@
             .imePadding()
             .verticalScroll(rememberScrollState())
     ) {
+        val clipboardManager = LocalClipboardManager.current
+        Button(onClick = { clipboardManager.setText(AnnotatedString("\uD801\uDC37")) }) {
+            Text("Copy surrogate pair \"\uD801\uDC37\"")
+        }
+
         TagLine(tag = "Visible")
         BasicSecureTextFieldDemo(TextObfuscationMode.Visible)
 
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/GraphicsSurfaceSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/GraphicsSurfaceSamples.kt
new file mode 100644
index 0000000..dc63c5f
--- /dev/null
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/GraphicsSurfaceSamples.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.samples
+
+import android.graphics.Rect
+import androidx.annotation.Sampled
+import androidx.compose.foundation.EmbeddedGraphicsSurface
+import androidx.compose.foundation.GraphicsSurface
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.withFrameNanos
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.unit.dp
+import kotlin.math.sin
+
+@Sampled
+@Composable
+fun GraphicsSurfaceColors() {
+    GraphicsSurface(
+        modifier = Modifier.fillMaxWidth().height(400.dp)
+    ) {
+        // Resources can be initialized/cached here
+
+        // A surface is available, we can start rendering
+        onSurface { surface, width, height ->
+            var w = width
+            var h = height
+
+            // Initial draw to avoid a black frame
+            surface.lockCanvas(Rect(0, 0, w, h)).apply {
+                drawColor(Color.Blue.toArgb())
+                surface.unlockCanvasAndPost(this)
+            }
+
+            // React to surface dimension changes
+            surface.onChanged { newWidth, newHeight ->
+                w = newWidth
+                h = newHeight
+            }
+
+            // Cleanup if needed
+            surface.onDestroyed {
+            }
+
+            // Render loop, automatically cancelled by GraphicsSurface
+            // on surface destruction
+            while (true) {
+                withFrameNanos { time ->
+                    surface.lockCanvas(Rect(0, 0, w, h)).apply {
+                        val timeMs = time / 1_000_000L
+                        val t = 0.5f + 0.5f * sin(timeMs / 1_000.0f)
+                        drawColor(lerp(Color.Blue, Color.Green, t).toArgb())
+                        surface.unlockCanvasAndPost(this)
+                    }
+                }
+            }
+        }
+    }
+}
+
+@Sampled
+@Composable
+fun EmbeddedGraphicsSurfaceColors() {
+    EmbeddedGraphicsSurface(
+        modifier = Modifier.fillMaxWidth().height(400.dp)
+    ) {
+        // Resources can be initialized/cached here
+
+        // A surface is available, we can start rendering
+        onSurface { surface, width, height ->
+            var w = width
+            var h = height
+
+            // Initial draw to avoid a black frame
+            surface.lockCanvas(Rect(0, 0, w, h)).apply {
+                drawColor(Color.Yellow.toArgb())
+                surface.unlockCanvasAndPost(this)
+            }
+
+            // React to surface dimension changes
+            surface.onChanged { newWidth, newHeight ->
+                w = newWidth
+                h = newHeight
+            }
+
+            // Cleanup if needed
+            surface.onDestroyed {
+            }
+
+            // Render loop, automatically cancelled by EmbeddedGraphicsSurface
+            // on surface destruction
+            while (true) {
+                withFrameNanos { time ->
+                    surface.lockCanvas(Rect(0, 0, w, h)).apply {
+                        val timeMs = time / 1_000_000L
+                        val t = 0.5f + 0.5f * sin(timeMs / 1_000.0f)
+                        drawColor(lerp(Color.Yellow, Color.Red, t).toArgb())
+                        surface.unlockCanvasAndPost(this)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/MagnifierSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/MagnifierSamples.kt
index fa88954..d928f08 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/MagnifierSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/MagnifierSamples.kt
@@ -16,9 +16,8 @@
 
 package androidx.compose.foundation.samples
 
+import android.os.Build
 import androidx.annotation.Sampled
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.MagnifierStyle
 import androidx.compose.foundation.gestures.detectDragGestures
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.magnifier
@@ -35,7 +34,6 @@
 import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.input.pointer.pointerInput
 
-@OptIn(ExperimentalFoundationApi::class)
 @Sampled
 @Composable
 fun MagnifierSample() {
@@ -43,7 +41,7 @@
     // Hide the magnifier until a drag starts.
     var magnifierCenter by remember { mutableStateOf(Offset.Unspecified) }
 
-    if (!MagnifierStyle.Default.isSupported) {
+    if (Build.VERSION.SDK_INT < 28) {
         Text("Magnifier is not supported on this platform.")
     } else {
         Box(
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/EmbeddedGraphicsSurfaceTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/EmbeddedGraphicsSurfaceTest.kt
new file mode 100644
index 0000000..6a10cd1
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/EmbeddedGraphicsSurfaceTest.kt
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation
+
+import android.graphics.PorterDuff
+import android.os.Build
+import android.view.Surface
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertPixels
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertHeightIsEqualTo
+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.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.unit.dp
+import androidx.core.graphics.ColorUtils
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import kotlin.math.roundToInt
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@RunWith(AndroidJUnit4::class)
+class EmbeddedGraphicsSurfaceTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    val size = 48.dp
+
+    @Test
+    fun testOnSurface() {
+        var surfaceRef: Surface? = null
+        var surfaceWidth = 0
+        var surfaceHeight = 0
+        var expectedSize = 0
+
+        rule.setContent {
+            expectedSize = with(LocalDensity.current) {
+                size.toPx().roundToInt()
+            }
+
+            EmbeddedGraphicsSurface(modifier = Modifier.size(size)) {
+                onSurface { surface, width, height ->
+                    surfaceRef = surface
+                    surfaceWidth = width
+                    surfaceHeight = height
+                }
+            }
+        }
+
+        rule.onRoot()
+            .assertWidthIsEqualTo(size)
+            .assertHeightIsEqualTo(size)
+            .assertIsDisplayed()
+
+        rule.runOnIdle {
+            assertNotNull(surfaceRef)
+            assertEquals(expectedSize, surfaceWidth)
+            assertEquals(expectedSize, surfaceHeight)
+        }
+    }
+
+    @Test
+    fun testOnSurfaceChanged() {
+        var surfaceWidth = 0
+        var surfaceHeight = 0
+        var expectedSize = 0
+
+        var desiredSize by mutableStateOf(size)
+
+        rule.setContent {
+            expectedSize = with(LocalDensity.current) {
+                desiredSize.toPx().roundToInt()
+            }
+
+            EmbeddedGraphicsSurface(modifier = Modifier.size(desiredSize)) {
+                onSurface { surface, initWidth, initHeight ->
+                    surfaceWidth = initWidth
+                    surfaceHeight = initHeight
+
+                    surface.onChanged { newWidth, newHeight ->
+                        surfaceWidth = newWidth
+                        surfaceHeight = newHeight
+                    }
+                }
+            }
+        }
+
+        rule.onRoot()
+            .assertWidthIsEqualTo(desiredSize)
+            .assertHeightIsEqualTo(desiredSize)
+
+        rule.runOnIdle {
+            assertEquals(expectedSize, surfaceWidth)
+            assertEquals(expectedSize, surfaceHeight)
+        }
+
+        desiredSize = size * 2
+        val prevSurfaceWidth = surfaceWidth
+        val prevSurfaceHeight = surfaceHeight
+
+        rule.onRoot()
+            .assertWidthIsEqualTo(desiredSize)
+            .assertHeightIsEqualTo(desiredSize)
+
+        rule.runOnIdle {
+            assertNotEquals(prevSurfaceWidth, surfaceWidth)
+            assertNotEquals(prevSurfaceHeight, surfaceHeight)
+            assertEquals(expectedSize, surfaceWidth)
+            assertEquals(expectedSize, surfaceHeight)
+        }
+    }
+
+    @Test
+    fun testOnSurfaceDestroyed() {
+        var surfaceRef: Surface? = null
+        var visible by mutableStateOf(true)
+
+        rule.setContent {
+            if (visible) {
+                EmbeddedGraphicsSurface(modifier = Modifier.size(size)) {
+                    onSurface { surface, _, _ ->
+                        surfaceRef = surface
+
+                        surface.onDestroyed {
+                            surfaceRef = null
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertNotNull(surfaceRef)
+        }
+
+        visible = false
+
+        rule.runOnIdle {
+            assertNull(surfaceRef)
+        }
+    }
+
+    @Test
+    fun testOnSurfaceRecreated() {
+        var surfaceCreatedCount = 0
+        var surfaceDestroyedCount = 0
+        var visible by mutableStateOf(true)
+
+        // NOTE: TextureView only destroys the surface when TextureView is detached from
+        // the window, and only creates when it gets attached to the window
+        rule.setContent {
+            if (visible) {
+                EmbeddedGraphicsSurface(modifier = Modifier.size(size)) {
+                    onSurface { surface, _, _ ->
+                        surfaceCreatedCount++
+                        surface.onDestroyed {
+                            surfaceDestroyedCount++
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertEquals(1, surfaceCreatedCount)
+            assertEquals(0, surfaceDestroyedCount)
+            visible = false
+        }
+
+        rule.runOnIdle {
+            assertEquals(1, surfaceCreatedCount)
+            assertEquals(1, surfaceDestroyedCount)
+            visible = true
+        }
+
+        rule.runOnIdle {
+            assertEquals(2, surfaceCreatedCount)
+            assertEquals(1, surfaceDestroyedCount)
+        }
+    }
+
+    @Test
+    fun testRender() {
+        var surfaceRef: Surface? = null
+        var expectedSize = 0
+
+        rule.setContent {
+            expectedSize = with(LocalDensity.current) {
+                size.toPx().roundToInt()
+            }
+            EmbeddedGraphicsSurface(modifier = Modifier.size(size)) {
+                onSurface { surface, _, _ ->
+                    surfaceRef = surface
+                    surface.lockHardwareCanvas().apply {
+                        drawColor(Color.Blue.toArgb())
+                        surface.unlockCanvasAndPost(this)
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertNotNull(surfaceRef)
+        }
+
+        surfaceRef!!
+            .captureToImage(expectedSize, expectedSize)
+            .assertPixels { Color.Blue }
+    }
+
+    @Test
+    fun testNotOpaque() {
+        val translucentRed = Color(1.0f, 0.0f, 0.0f, 0.5f).toArgb()
+
+        rule.setContent {
+            Box(modifier = Modifier.size(size)) {
+                Canvas(modifier = Modifier.size(size)) {
+                    drawRect(Color.White)
+                }
+                EmbeddedGraphicsSurface(
+                    modifier = Modifier
+                        .size(size)
+                        .testTag("EmbeddedGraphicSurface"),
+                    isOpaque = false
+                ) {
+                    onSurface { surface, _, _ ->
+                        surface.lockHardwareCanvas().apply {
+                            drawColor(0x00000000, PorterDuff.Mode.CLEAR)
+                            drawColor(translucentRed)
+                            surface.unlockCanvasAndPost(this)
+                        }
+                    }
+                }
+            }
+        }
+
+        val expectedColor = Color(ColorUtils.compositeColors(translucentRed, Color.White.toArgb()))
+
+        rule
+            .onNodeWithTag("EmbeddedGraphicSurface")
+            .captureToImage()
+            .assertPixels { expectedColor }
+    }
+
+    @Test
+    fun testOpaque() {
+        rule.setContent {
+            Box(modifier = Modifier.size(size)) {
+                Canvas(modifier = Modifier.size(size)) {
+                    drawRect(Color.Green)
+                }
+                EmbeddedGraphicsSurface(
+                    modifier = Modifier
+                        .size(size)
+                        .testTag("EmbeddedGraphicSurface")
+                ) {
+                    onSurface { surface, _, _ ->
+                        surface.lockHardwareCanvas().apply {
+                            drawColor(Color.Red.toArgb())
+                            surface.unlockCanvasAndPost(this)
+                        }
+                    }
+                }
+            }
+        }
+
+        rule
+            .onNodeWithTag("EmbeddedGraphicSurface")
+            .captureToImage()
+            .assertPixels { Color.Red }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/GraphicsSurfaceTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/GraphicsSurfaceTest.kt
new file mode 100644
index 0000000..6fc417c
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/GraphicsSurfaceTest.kt
@@ -0,0 +1,589 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation
+
+import android.graphics.Bitmap
+import android.graphics.PorterDuff
+import android.graphics.Rect
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.view.Choreographer
+import android.view.PixelCopy
+import android.view.Surface
+import android.view.View
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.withFrameNanos
+import androidx.compose.testutils.assertPixels
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.ViewRootForTest
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.unit.dp
+import androidx.concurrent.futures.ResolvableFuture
+import androidx.core.graphics.ColorUtils
+import androidx.core.graphics.createBitmap
+import androidx.test.core.internal.os.HandlerExecutor
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeUnit
+import kotlin.math.roundToInt
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
+@RunWith(AndroidJUnit4::class)
+class GraphicsSurfaceTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    val size = 48.dp
+
+    @Test
+    fun testOnSurface() {
+        var surfaceRef: Surface? = null
+        var surfaceWidth = 0
+        var surfaceHeight = 0
+        var expectedSize = 0
+
+        rule.setContent {
+            expectedSize = with(LocalDensity.current) {
+                size.toPx().roundToInt()
+            }
+
+            GraphicsSurface(modifier = Modifier.size(size)) {
+                onSurface { surface, width, height ->
+                    surfaceRef = surface
+                    surfaceWidth = width
+                    surfaceHeight = height
+                }
+            }
+        }
+
+        rule.onRoot()
+            .assertWidthIsEqualTo(size)
+            .assertHeightIsEqualTo(size)
+            .assertIsDisplayed()
+
+        rule.runOnIdle {
+            assertNotNull(surfaceRef)
+            assertEquals(expectedSize, surfaceWidth)
+            assertEquals(expectedSize, surfaceHeight)
+        }
+    }
+
+    @Test
+    fun testOnSurfaceChanged() {
+        var surfaceWidth = 0
+        var surfaceHeight = 0
+        var expectedSize = 0
+
+        var desiredSize by mutableStateOf(size)
+
+        rule.setContent {
+            expectedSize = with(LocalDensity.current) {
+                desiredSize.toPx().roundToInt()
+            }
+
+            GraphicsSurface(modifier = Modifier.size(desiredSize)) {
+                onSurface { surface, _, _ ->
+                    surface.onChanged { newWidth, newHeight ->
+                        surfaceWidth = newWidth
+                        surfaceHeight = newHeight
+                    }
+                }
+            }
+        }
+
+        rule.onRoot()
+            .assertWidthIsEqualTo(desiredSize)
+            .assertHeightIsEqualTo(desiredSize)
+
+        // onChanged() hasn't been called yet
+        rule.runOnIdle {
+            assertEquals(0, surfaceWidth)
+            assertEquals(0, surfaceHeight)
+        }
+
+        desiredSize = size * 2
+        val prevSurfaceWidth = surfaceWidth
+        val prevSurfaceHeight = surfaceHeight
+
+        rule.onRoot()
+            .assertWidthIsEqualTo(desiredSize)
+            .assertHeightIsEqualTo(desiredSize)
+
+        rule.runOnIdle {
+            assertNotEquals(prevSurfaceWidth, surfaceWidth)
+            assertNotEquals(prevSurfaceHeight, surfaceHeight)
+            assertEquals(expectedSize, surfaceWidth)
+            assertEquals(expectedSize, surfaceHeight)
+        }
+    }
+
+    @Test
+    fun testOnSurfaceDestroyed() {
+        var surfaceRef: Surface? = null
+        var visible by mutableStateOf(true)
+
+        rule.setContent {
+            if (visible) {
+                GraphicsSurface(modifier = Modifier.size(size)) {
+                    onSurface { surface, _, _ ->
+                        surfaceRef = surface
+
+                        surface.onDestroyed {
+                            surfaceRef = null
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertNotNull(surfaceRef)
+        }
+
+        visible = false
+
+        rule.runOnIdle {
+            assertNull(surfaceRef)
+        }
+    }
+
+    @Test
+    fun testOnSurfaceRecreated() {
+        var surfaceCreatedCount = 0
+        var surfaceDestroyedCount = 0
+
+        var view: View? = null
+
+        rule.setContent {
+            view = LocalView.current
+            GraphicsSurface(modifier = Modifier.size(size)) {
+                onSurface { surface, _, _ ->
+                    surfaceCreatedCount++
+                    surface.onDestroyed {
+                        surfaceDestroyedCount++
+                    }
+                }
+            }
+        }
+
+        // NOTE: SurfaceView only triggers a Surface destroy/create cycle on visibility
+        // change if its *own* visibility or the visibility of the window changes. Here
+        // we change the visibility of the window by setting the visibility of the root
+        // view (the host view in ViewRootImpl).
+        rule.runOnIdle {
+            assertEquals(1, surfaceCreatedCount)
+            assertEquals(0, surfaceDestroyedCount)
+            view?.rootView?.visibility = View.INVISIBLE
+        }
+
+        rule.runOnIdle {
+            assertEquals(1, surfaceCreatedCount)
+            assertEquals(1, surfaceDestroyedCount)
+            view?.rootView?.visibility = View.VISIBLE
+        }
+
+        rule.runOnIdle {
+            assertEquals(2, surfaceCreatedCount)
+            assertEquals(1, surfaceDestroyedCount)
+        }
+    }
+
+    @Test
+    fun testRender() {
+        var surfaceRef: Surface? = null
+        var expectedSize = 0
+
+        rule.setContent {
+            expectedSize = with(LocalDensity.current) {
+                size.toPx().roundToInt()
+            }
+            GraphicsSurface(modifier = Modifier.size(size)) {
+                onSurface { surface, _, _ ->
+                    surfaceRef = surface
+                    surface.lockHardwareCanvas().apply {
+                        drawColor(Color.Blue.toArgb())
+                        surface.unlockCanvasAndPost(this)
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertNotNull(surfaceRef)
+        }
+
+        surfaceRef!!
+            .captureToImage(expectedSize, expectedSize)
+            .assertPixels { Color.Blue }
+    }
+
+    @Test
+    fun testZOrderDefault() {
+        val frameCount = 4
+        val latch = CountDownLatch(frameCount)
+
+        rule.setContent {
+            Box(modifier = Modifier.size(size)) {
+                GraphicsSurface(
+                    modifier = Modifier
+                        .size(size)
+                        .testTag("GraphicSurface")
+                ) {
+                    onSurface { surface, _, _ ->
+                        // Draw > 3 frames to make sure the screenshot copy will pick up
+                        // a SurfaceFlinger composition that includes our Surface
+                        repeat(frameCount) {
+                            withFrameNanos {
+                                surface.lockHardwareCanvas().apply {
+                                    drawColor(Color.Blue.toArgb())
+                                    surface.unlockCanvasAndPost(this)
+                                }
+                                latch.countDown()
+                            }
+                        }
+                    }
+                }
+                Canvas(modifier = Modifier.size(size)) {
+                    drawRect(Color.Green)
+                }
+            }
+        }
+
+        if (!latch.await(5, TimeUnit.SECONDS)) {
+            throw AssertionError("Failed waiting for render")
+        }
+
+        rule
+            .onNodeWithTag("GraphicSurface")
+            .screenshotToImage()!!
+            .assertPixels { Color.Green }
+    }
+
+    @Test
+    fun testZOrderMediaOverlay() {
+        val frameCount = 4
+        val latch = CountDownLatch(frameCount)
+
+        rule.setContent {
+            Box(modifier = Modifier.size(size)) {
+                GraphicsSurface(
+                    modifier = Modifier.size(size),
+                    zOrder = GraphicsSurfaceZOrder.Behind
+                ) {
+                    onSurface { surface, _, _ ->
+                        // Draw > 3 frames to make sure the screenshot copy will pick up
+                        // a SurfaceFlinger composition that includes our Surface
+                        repeat(frameCount) {
+                            withFrameNanos {
+                                surface.lockHardwareCanvas().apply {
+                                    drawColor(Color.Blue.toArgb())
+                                    surface.unlockCanvasAndPost(this)
+                                }
+                                latch.countDown()
+                            }
+                        }
+                    }
+                }
+                GraphicsSurface(
+                    modifier = Modifier
+                        .size(size)
+                        .testTag("GraphicSurface"),
+                    zOrder = GraphicsSurfaceZOrder.MediaOverlay
+                ) {
+                    onSurface { surface, _, _ ->
+                        surface.lockHardwareCanvas().apply {
+                            drawColor(Color.Red.toArgb())
+                            surface.unlockCanvasAndPost(this)
+                        }
+                        latch.countDown()
+                    }
+                }
+            }
+        }
+
+        if (!latch.await(5, TimeUnit.SECONDS)) {
+            throw AssertionError("Failed waiting for render")
+        }
+
+        rule
+            .onNodeWithTag("GraphicSurface")
+            .screenshotToImage()!!
+            .assertPixels { Color.Red }
+    }
+
+    @Test
+    fun testZOrderOnTop() {
+        val frameCount = 4
+        val latch = CountDownLatch(frameCount)
+
+        rule.setContent {
+            Box(modifier = Modifier.size(size)) {
+                GraphicsSurface(
+                    modifier = Modifier
+                        .size(size)
+                        .testTag("GraphicSurface"),
+                    zOrder = GraphicsSurfaceZOrder.OnTop
+                ) {
+                    onSurface { surface, _, _ ->
+                        // Draw > 3 frames to make sure the screenshot copy will pick up
+                        // a SurfaceFlinger composition that includes our Surface
+                        repeat(frameCount) {
+                            withFrameNanos {
+                                surface.lockHardwareCanvas().apply {
+                                    drawColor(Color.Blue.toArgb())
+                                    surface.unlockCanvasAndPost(this)
+                                }
+                                latch.countDown()
+                            }
+                        }
+                    }
+                }
+                Canvas(modifier = Modifier.size(size)) {
+                    drawRect(Color.Green)
+                }
+            }
+        }
+
+        if (!latch.await(5, TimeUnit.SECONDS)) {
+            throw AssertionError("Failed waiting for render")
+        }
+
+        rule
+            .onNodeWithTag("GraphicSurface")
+            .screenshotToImage()!!
+            .assertPixels { Color.Blue }
+    }
+
+    @Test
+    fun testNotOpaque() {
+        val frameCount = 4
+        val latch = CountDownLatch(frameCount)
+        val translucentRed = Color(1.0f, 0.0f, 0.0f, 0.5f).toArgb()
+
+        rule.setContent {
+            Box(modifier = Modifier.size(size)) {
+                GraphicsSurface(
+                    modifier = Modifier
+                        .size(size)
+                        .testTag("GraphicSurface"),
+                    isOpaque = false,
+                    zOrder = GraphicsSurfaceZOrder.OnTop
+                ) {
+                    onSurface { surface, _, _ ->
+                        // Draw > 3 frames to make sure the screenshot copy will pick up
+                        // a SurfaceFlinger composition that includes our Surface
+                        repeat(frameCount) {
+                            withFrameNanos {
+                                surface.lockHardwareCanvas().apply {
+                                    // Since we are drawing a translucent color we need to
+                                    // clear first
+                                    drawColor(0x00000000, PorterDuff.Mode.CLEAR)
+                                    drawColor(translucentRed)
+                                    surface.unlockCanvasAndPost(this)
+                                }
+                                latch.countDown()
+                            }
+                        }
+                    }
+                }
+                Canvas(modifier = Modifier.size(size)) {
+                    drawRect(Color.White)
+                }
+            }
+        }
+
+        if (!latch.await(5, TimeUnit.SECONDS)) {
+            throw AssertionError("Failed waiting for render")
+        }
+
+        val expectedColor = Color(ColorUtils.compositeColors(translucentRed, Color.White.toArgb()))
+
+        rule
+            .onNodeWithTag("GraphicSurface")
+            .screenshotToImage()!!
+            .assertPixels { expectedColor }
+    }
+
+    @Test
+    fun testSecure() {
+        val frameCount = 4
+        val latch = CountDownLatch(frameCount)
+
+        rule.setContent {
+            GraphicsSurface(
+                modifier = Modifier
+                    .size(size)
+                    .testTag("GraphicSurface"),
+                isSecure = true
+            ) {
+                onSurface { surface, _, _ ->
+                    // Draw > 3 frames to make sure the screenshot copy will pick up
+                    // a SurfaceFlinger composition that includes our Surface
+                    repeat(frameCount) {
+                        withFrameNanos {
+                            surface.lockHardwareCanvas().apply {
+                                drawColor(Color.Blue.toArgb())
+                                surface.unlockCanvasAndPost(this)
+                            }
+                            latch.countDown()
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!latch.await(5, TimeUnit.SECONDS)) {
+            throw AssertionError("Failed waiting for render")
+        }
+
+        val screen = rule
+            .onNodeWithTag("GraphicSurface")
+            .screenshotToImage(true)
+
+        // Before API 33 taking a screenshot with a secure surface returns null
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+            assertNull(screen)
+        } else {
+            screen!!.assertPixels { Color.Black }
+        }
+    }
+}
+
+/**
+ * Returns an ImageBitmap containing a screenshot of the device. On API < 33,
+ * a secure surface present on screen can cause this function to return null.
+ */
+private fun SemanticsNodeInteraction.screenshotToImage(
+    hasSecureSurfaces: Boolean = false
+): ImageBitmap? {
+    val instrumentation = InstrumentationRegistry.getInstrumentation()
+    instrumentation.waitForIdleSync()
+
+    val uiAutomation = instrumentation.uiAutomation
+
+    val node = fetchSemanticsNode()
+    val view = (node.root as ViewRootForTest).view
+
+    val bitmapFuture: ResolvableFuture<Bitmap> = ResolvableFuture.create()
+
+    val mainExecutor = HandlerExecutor(Handler(Looper.getMainLooper()))
+    mainExecutor.execute {
+        Choreographer.getInstance().postFrameCallback {
+            val location = IntArray(2)
+            view.getLocationOnScreen(location)
+
+            val bounds = node.boundsInRoot.translate(
+                location[0].toFloat(),
+                location[1].toFloat()
+            )
+
+            // do multiple retries of uiAutomation.takeScreenshot because it is known to return null
+            // on API 31+ b/257274080
+            var bitmap: Bitmap? = null
+            var i = 0
+            while (i < 3 && bitmap == null) {
+                bitmap = uiAutomation.takeScreenshot()
+                i++
+            }
+
+            if (bitmap != null) {
+                bitmap = Bitmap.createBitmap(
+                    bitmap,
+                    bounds.left.toInt(),
+                    bounds.top.toInt(),
+                    bounds.width.toInt(),
+                    bounds.height.toInt()
+                )
+                bitmapFuture.set(bitmap)
+            } else {
+                if (hasSecureSurfaces) {
+                    // may be null on older API levels when a secure surface is showing
+                    bitmapFuture.set(null)
+                }
+                // if we don't show secure surfaces, let the future timeout on get()
+            }
+        }
+    }
+
+    return try {
+        bitmapFuture.get(5, TimeUnit.SECONDS)?.asImageBitmap()
+    } catch (e: ExecutionException) {
+        null
+    }
+}
+
+@RequiresApi(Build.VERSION_CODES.O)
+internal fun Surface.captureToImage(width: Int, height: Int): ImageBitmap {
+    val bitmap = createBitmap(width, height)
+
+    val latch = CountDownLatch(1)
+    var copyResult = 0
+    val onCopyFinished = PixelCopy.OnPixelCopyFinishedListener { result ->
+        copyResult = result
+        latch.countDown()
+        android.util.Log.d("Test", Thread.currentThread().toString())
+    }
+
+    PixelCopy.request(
+        this,
+        Rect(0, 0, width, height),
+        bitmap,
+        onCopyFinished,
+        Handler(Looper.getMainLooper())
+    )
+
+    if (!latch.await(1, TimeUnit.SECONDS)) {
+        throw AssertionError("Failed waiting for PixelCopy!")
+    }
+
+    if (copyResult != PixelCopy.SUCCESS) {
+        throw AssertionError("PixelCopy failed!")
+    }
+
+    return bitmap.asImageBitmap()
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/MagnifierTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/MagnifierTest.kt
index 89438cc..c19d2d2 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/MagnifierTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/MagnifierTest.kt
@@ -18,7 +18,6 @@
 
 import android.annotation.SuppressLint
 import android.view.View
-import androidx.compose.foundation.MagnifierStyle.Companion.isStyleSupported
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.offset
@@ -39,6 +38,7 @@
 import androidx.compose.ui.test.SemanticsMatcher.Companion.keyIsDefined
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
@@ -54,7 +54,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalFoundationApi::class)
 @SuppressLint("NewApi")
 @MediumTest
 @RunWith(AndroidJUnit4::class)
@@ -73,133 +72,6 @@
         isDebugInspectorInfoEnabled = false
     }
 
-    @Test
-    fun magnifierStyle_equal() {
-        val configuration1 = MagnifierStyle(
-            size = DpSize(1.dp, 1.dp),
-            cornerRadius = 1.dp,
-            elevation = 1.dp,
-            clippingEnabled = true,
-            fishEyeEnabled = true
-        )
-        val configuration2 = MagnifierStyle(
-            size = DpSize(1.dp, 1.dp),
-            cornerRadius = 1.dp,
-            elevation = 1.dp,
-            clippingEnabled = true,
-            fishEyeEnabled = true
-        )
-
-        assertThat(configuration1).isEqualTo(configuration2)
-    }
-
-    @Test
-    fun magnifierStyle_notEqualSize() {
-        val configuration1 = MagnifierStyle(
-            size = DpSize(1.dp, 1.dp),
-            cornerRadius = 1.dp,
-            elevation = 1.dp,
-            clippingEnabled = true,
-            fishEyeEnabled = true
-        )
-        val configuration2 = MagnifierStyle(
-            size = DpSize(1.dp, 2.dp),
-            cornerRadius = 1.dp,
-            elevation = 1.dp,
-            clippingEnabled = true,
-            fishEyeEnabled = true
-        )
-
-        assertThat(configuration1).isNotEqualTo(configuration2)
-    }
-
-    @Test
-    fun magnifierStyle_hashCodeEqual_whenEqual() {
-        val configuration1 = MagnifierStyle(
-            size = DpSize(1.dp, 1.dp),
-            cornerRadius = 1.dp,
-            elevation = 1.dp,
-            clippingEnabled = true,
-            fishEyeEnabled = true
-        )
-        val configuration2 = MagnifierStyle(
-            size = DpSize(1.dp, 1.dp),
-            cornerRadius = 1.dp,
-            elevation = 1.dp,
-            clippingEnabled = true,
-            fishEyeEnabled = true
-        )
-
-        assertThat(configuration1.hashCode()).isEqualTo(configuration2.hashCode())
-    }
-
-    @Test
-    fun magnifierStyle_hashCodeNotEqual_whenNotEqual() {
-        val configuration1 = MagnifierStyle(
-            size = DpSize(1.dp, 1.dp),
-            cornerRadius = 1.dp,
-            elevation = 1.dp,
-            clippingEnabled = true,
-            fishEyeEnabled = true
-        )
-        val configuration2 = MagnifierStyle(
-            size = DpSize(1.dp, 2.dp),
-            cornerRadius = 1.dp,
-            elevation = 1.dp,
-            clippingEnabled = true,
-            fishEyeEnabled = true
-        )
-
-        assertThat(configuration1.hashCode()).isNotEqualTo(configuration2.hashCode())
-    }
-
-    @Test
-    fun magnifierStyle_toString_whenNotTextDefault() {
-        assertThat(MagnifierStyle.Default.toString()).isEqualTo(
-            "MagnifierStyle(" +
-                "size=DpSize.Unspecified, " +
-                "cornerRadius=Dp.Unspecified, " +
-                "elevation=Dp.Unspecified, " +
-                "clippingEnabled=true, " +
-                "fishEyeEnabled=false)"
-        )
-    }
-
-    @Test
-    fun magnifierStyle_toString_whenTextDefault() {
-        assertThat(MagnifierStyle.TextDefault.toString()).isEqualTo("MagnifierStyle.TextDefault")
-    }
-
-    @Test
-    fun magnifierStyle_isSupported() {
-        // Never supported on old versions.
-        assertThat(isStyleSupported(MagnifierStyle.Default, sdkVersion = 21)).isFalse()
-        assertThat(isStyleSupported(MagnifierStyle.Default, sdkVersion = 27)).isFalse()
-        assertThat(isStyleSupported(MagnifierStyle.TextDefault, sdkVersion = 27)).isFalse()
-
-        // Defaults supported on lowest supported version.
-        assertThat(isStyleSupported(MagnifierStyle.Default, sdkVersion = 28)).isTrue()
-        assertThat(isStyleSupported(MagnifierStyle.TextDefault, sdkVersion = 28)).isTrue()
-        assertThat(isStyleSupported(MagnifierStyle(), sdkVersion = 28)).isTrue()
-
-        // Custom styles only available after 28.
-        assertThat(
-            isStyleSupported(
-                MagnifierStyle(cornerRadius = 42.dp),
-                sdkVersion = 28
-            )
-        ).isFalse()
-        assertThat(isStyleSupported(MagnifierStyle(cornerRadius = 42.dp), sdkVersion = 29)).isTrue()
-
-        // Fisheye is never supported (yet, see b/202451044).
-        assertThat(
-            isStyleSupported(
-                MagnifierStyle(fishEyeEnabled = true),
-                sdkVersion = 9999
-            )
-        ).isFalse()
-    }
-
     @SdkSuppress(minSdkVersion = 28)
     @Test
     fun magnifier_inspectorValue_whenSupported() {
@@ -215,7 +87,10 @@
             ValueElement("sourceCenter", sourceCenterLambda),
             ValueElement("magnifierCenter", magnifierCenterLambda),
             ValueElement("zoom", Float.NaN),
-            ValueElement("style", MagnifierStyle.Default),
+            ValueElement("size", DpSize.Unspecified),
+            ValueElement("cornerRadius", Dp.Unspecified),
+            ValueElement("elevation", Dp.Unspecified),
+            ValueElement("clippingEnabled", true),
         )
     }
 
@@ -234,7 +109,10 @@
             ValueElement("sourceCenter", sourceCenterLambda),
             ValueElement("magnifierCenter", magnifierCenterLambda),
             ValueElement("zoom", Float.NaN),
-            ValueElement("style", MagnifierStyle.Default),
+            ValueElement("size", DpSize.Unspecified),
+            ValueElement("cornerRadius", Dp.Unspecified),
+            ValueElement("elevation", Dp.Unspecified),
+            ValueElement("clippingEnabled", true),
         )
     }
 
@@ -265,7 +143,6 @@
                         sourceCenter = { Offset.Zero },
                         magnifierCenter = { Offset.Unspecified },
                         zoom = Float.NaN,
-                        style = MagnifierStyle.Default,
                         onSizeChanged = null,
                         platformMagnifierFactory = magnifierFactory
                     )
@@ -288,14 +165,14 @@
     @Test
     fun platformMagnifierModifier_recreatesMagnifier_whenConfigurationChanged() {
         val magnifierFactory = CountingPlatformMagnifierFactory()
-        var configuration by mutableStateOf(MagnifierStyle(elevation = 1.dp))
+        var elevation by mutableStateOf(1.dp)
         rule.setContent {
             Box(
                 Modifier.magnifier(
                     sourceCenter = { Offset.Zero },
                     magnifierCenter = { Offset.Unspecified },
                     zoom = Float.NaN,
-                    style = configuration,
+                    elevation = elevation,
                     onSizeChanged = null,
                     platformMagnifierFactory = magnifierFactory
                 )
@@ -306,7 +183,7 @@
             assertThat(magnifierFactory.creationCount).isEqualTo(1)
         }
 
-        configuration = MagnifierStyle(elevation = configuration.elevation * 2)
+        elevation *= 2
 
         rule.runOnIdle {
             assertThat(magnifierFactory.creationCount).isEqualTo(2)
@@ -317,14 +194,14 @@
     @Test
     fun platformMagnifierModifier_recreatesMagnifier_whenConfigurationChangedToText() {
         val magnifierFactory = CountingPlatformMagnifierFactory()
-        var style: MagnifierStyle by mutableStateOf(MagnifierStyle.Default)
+        var useTextDefault by mutableStateOf(false)
         rule.setContent {
             Box(
                 Modifier.magnifier(
                     sourceCenter = { Offset.Zero },
                     magnifierCenter = { Offset.Unspecified },
                     zoom = Float.NaN,
-                    style = style,
+                    useTextDefault = useTextDefault,
                     onSizeChanged = null,
                     platformMagnifierFactory = magnifierFactory
                 )
@@ -335,7 +212,7 @@
             assertThat(magnifierFactory.creationCount).isEqualTo(1)
         }
 
-        style = MagnifierStyle.TextDefault
+        useTextDefault = true
 
         rule.runOnIdle {
             assertThat(magnifierFactory.creationCount).isEqualTo(2)
@@ -353,7 +230,6 @@
                     sourceCenter = { Offset.Zero },
                     magnifierCenter = { Offset.Unspecified },
                     zoom = zoom,
-                    style = MagnifierStyle.Default,
                     onSizeChanged = null,
                     platformMagnifierFactory = magnifierFactory
                 )
@@ -382,7 +258,6 @@
                     sourceCenter = { Offset.Zero },
                     magnifierCenter = { Offset.Unspecified },
                     zoom = zoom,
-                    style = MagnifierStyle.Default,
                     onSizeChanged = null,
                     platformMagnifierFactory = magnifierFactory
 
@@ -419,7 +294,6 @@
                         sourceCenter = { Offset.Zero },
                         magnifierCenter = { Offset.Unspecified },
                         zoom = Float.NaN,
-                        style = MagnifierStyle.Default,
                         onSizeChanged = null,
                         platformMagnifierFactory = PlatformMagnifierFactory(platformMagnifier)
                     )
@@ -456,7 +330,6 @@
                         sourceCenter = { Offset.Zero },
                         magnifierCenter = { Offset.Unspecified },
                         zoom = Float.NaN,
-                        style = MagnifierStyle.Default,
                         onSizeChanged = null,
                         platformMagnifierFactory = PlatformMagnifierFactory(platformMagnifier)
                     )
@@ -487,7 +360,6 @@
                         sourceCenter = { Offset.Zero },
                         magnifierCenter = { Offset.Unspecified },
                         zoom = Float.NaN,
-                        style = MagnifierStyle.Default,
                         onSizeChanged = null,
                         platformMagnifierFactory = PlatformMagnifierFactory(platformMagnifier)
                     )
@@ -516,7 +388,6 @@
                     sourceCenter = { sourceCenter },
                     magnifierCenter = { Offset.Unspecified },
                     zoom = Float.NaN,
-                    style = MagnifierStyle.Default,
                     onSizeChanged = null,
                     platformMagnifierFactory = PlatformMagnifierFactory(platformMagnifier)
                 )
@@ -545,7 +416,6 @@
                     sourceCenter = { Offset.Zero },
                     magnifierCenter = { magnifierCenter },
                     zoom = Float.NaN,
-                    style = MagnifierStyle.Default,
                     onSizeChanged = null,
                     platformMagnifierFactory = PlatformMagnifierFactory(platformMagnifier)
                 )
@@ -574,7 +444,6 @@
                     sourceCenter = { Offset.Zero },
                     magnifierCenter = { Offset.Unspecified },
                     zoom = zoom,
-                    style = MagnifierStyle.Default,
                     onSizeChanged = null,
                     platformMagnifierFactory = PlatformMagnifierFactory(
                         platformMagnifier,
@@ -607,7 +476,6 @@
                         sourceCenter = { Offset.Zero },
                         magnifierCenter = { Offset.Unspecified },
                         zoom = Float.NaN,
-                        style = MagnifierStyle.Default,
                         onSizeChanged = null,
                         platformMagnifierFactory = PlatformMagnifierFactory(platformMagnifier)
                     )
@@ -639,7 +507,6 @@
                     sourceCenter = { sourceCenter },
                     magnifierCenter = { Offset.Unspecified },
                     zoom = Float.NaN,
-                    style = MagnifierStyle.Default,
                     onSizeChanged = null,
                     platformMagnifierFactory = PlatformMagnifierFactory(platformMagnifier)
                 )
@@ -663,7 +530,7 @@
     @SdkSuppress(minSdkVersion = 28)
     @Test
     fun platformMagnifierModifier_dismissesMagnifier_whenMagnifierRecreated() {
-        var configuration by mutableStateOf(MagnifierStyle(elevation = 1.dp))
+        var elevation by mutableStateOf(1.dp)
         val platformMagnifier = CountingPlatformMagnifier()
         rule.setContent {
             Box(
@@ -671,7 +538,7 @@
                     sourceCenter = { Offset.Zero },
                     magnifierCenter = { Offset.Unspecified },
                     zoom = Float.NaN,
-                    style = configuration,
+                    elevation = elevation,
                     onSizeChanged = null,
                     platformMagnifierFactory = PlatformMagnifierFactory(platformMagnifier)
                 )
@@ -680,7 +547,7 @@
 
         val initialDismissCount = rule.runOnIdle { platformMagnifier.dismissCount }
 
-        configuration = MagnifierStyle(elevation = configuration.elevation + 1.dp)
+        elevation += 1.dp
 
         rule.runOnIdle {
             assertThat(platformMagnifier.dismissCount).isEqualTo(initialDismissCount + 1)
@@ -701,7 +568,6 @@
                     sourceCenter = { Offset.Zero },
                     magnifierCenter = { Offset.Unspecified },
                     zoom = Float.NaN,
-                    style = MagnifierStyle.Default,
                     onSizeChanged = { sizeEvents += it },
                     platformMagnifierFactory = PlatformMagnifierFactory(platformMagnifier)
                 )
@@ -731,7 +597,6 @@
                     sourceCenter = { Offset.Unspecified },
                     magnifierCenter = { Offset.Unspecified },
                     zoom = Float.NaN,
-                    style = MagnifierStyle.Default,
                     onSizeChanged = { sizeEvents += it },
                     platformMagnifierFactory = PlatformMagnifierFactory(platformMagnifier)
                 )
@@ -768,7 +633,7 @@
                     sourceCenter = { Offset.Zero },
                     magnifierCenter = { Offset.Unspecified },
                     zoom = Float.NaN,
-                    style = MagnifierStyle(size = magnifierDpSize),
+                    size = magnifierDpSize,
                     onSizeChanged = { sizeEvents += it },
                     platformMagnifierFactory = PlatformMagnifierFactory(platformMagnifier)
                 )
@@ -820,8 +685,12 @@
     ) = object : PlatformMagnifierFactory {
         override val canUpdateZoom: Boolean = canUpdateZoom
         override fun create(
-            style: MagnifierStyle,
             view: View,
+            useTextDefault: Boolean,
+            size: DpSize,
+            cornerRadius: Dp,
+            elevation: Dp,
+            clippingEnabled: Boolean,
             density: Density,
             initialZoom: Float
         ): PlatformMagnifier {
@@ -838,8 +707,12 @@
         var creationCount = 0
 
         override fun create(
-            style: MagnifierStyle,
             view: View,
+            useTextDefault: Boolean,
+            size: DpSize,
+            cornerRadius: Dp,
+            elevation: Dp,
+            clippingEnabled: Boolean,
             density: Density,
             initialZoom: Float
         ): PlatformMagnifier {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/PlatformMagnifierTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/PlatformMagnifierTest.kt
index 4ed6ab0..b2c7a87 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/PlatformMagnifierTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/PlatformMagnifierTest.kt
@@ -17,6 +17,8 @@
 package androidx.compose.foundation
 
 import android.graphics.Point
+import android.os.Build
+import androidx.annotation.RequiresApi
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.runtime.DisposableEffect
@@ -25,6 +27,7 @@
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
@@ -39,7 +42,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalFoundationApi::class)
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 class PlatformMagnifierTest {
@@ -179,6 +181,7 @@
         assertThat(magnifier.size).isEqualTo(magnifierSize)
     }
 
+    @RequiresApi(Build.VERSION_CODES.P)
     private fun createAndroidPlatformMagnifier(
         size: DpSize = DpSize.Unspecified
     ): PlatformMagnifierFactoryApi28Impl.PlatformMagnifierImpl {
@@ -195,7 +198,11 @@
                         view = currentView,
                         density = density,
                         initialZoom = Float.NaN,
-                        style = MagnifierStyle(size = size),
+                        useTextDefault = false,
+                        size = size,
+                        cornerRadius = Dp.Unspecified,
+                        elevation = Dp.Unspecified,
+                        clippingEnabled = true,
                     ) as PlatformMagnifierFactoryApi28Impl.PlatformMagnifierImpl
                     onDispose {}
                 }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index b5fe0b2..e05e7d7 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -35,10 +35,15 @@
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentWidth
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.rememberLazyListState
@@ -61,6 +66,7 @@
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.key.Key
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -86,8 +92,11 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performKeyInput
 import androidx.compose.ui.test.performMouseInput
 import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.test.requestFocus
 import androidx.compose.ui.test.swipe
 import androidx.compose.ui.test.swipeDown
 import androidx.compose.ui.test.swipeLeft
@@ -243,6 +252,87 @@
         }
     }
 
+    /*
+     * Note: For keyboard scrolling to work (that is, scrolling based on the page up/down keys),
+     * at least one child within the scrollable must be focusable. (This matches the behavior in
+     * Views.)
+     */
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun scrollable_horizontalScroll_keyboardPageUpAndDown() {
+        var scrollAmount = 0f
+
+        val scrollableState = ScrollableState(
+            consumeScrollDelta = {
+                scrollAmount += it
+                it
+            }
+        )
+
+        rule.setContent {
+            Row(
+                Modifier
+                    .fillMaxHeight()
+                    .wrapContentWidth()
+                    .background(Color.Red)
+                    .scrollable(
+                        state = scrollableState,
+                        orientation = Orientation.Horizontal
+                    )
+                    .padding(10.dp)
+            ) {
+                Box(
+                    modifier = Modifier
+                        .fillMaxHeight()
+                        .testTag(scrollableBoxTag)
+                        .width(50.dp)
+                        .background(Color.Blue)
+                        // Required for keyboard scrolling (page up/down keys) to work.
+                        .focusable()
+                        .padding(10.dp)
+                )
+
+                Spacer(modifier = Modifier.size(10.dp))
+
+                for (i in 0 until 40) {
+                    val color = if (i % 2 == 0) {
+                        Color.Yellow
+                    } else {
+                        Color.Green
+                    }
+
+                    Box(
+                        modifier = Modifier
+                            .fillMaxHeight()
+                            .width(50.dp)
+                            .background(color)
+                            .padding(10.dp)
+                    )
+                    Spacer(modifier = Modifier.size(10.dp))
+                }
+            }
+        }
+
+        rule.onNodeWithTag(scrollableBoxTag).requestFocus()
+        rule.onNodeWithTag(scrollableBoxTag).performKeyInput {
+            pressKey(Key.PageDown)
+        }
+
+        rule.runOnIdle {
+            assertThat(scrollAmount).isLessThan(0f)
+        }
+
+        scrollAmount = 0f
+
+        rule.onNodeWithTag(scrollableBoxTag).performKeyInput {
+            pressKey(Key.PageUp)
+        }
+
+        rule.runOnIdle {
+            assertThat(scrollAmount).isGreaterThan(0f)
+        }
+    }
+
     @Test
     fun scrollable_horizontalScroll_reverse() {
         var total = 0f
@@ -423,6 +513,86 @@
         }
     }
 
+    /*
+     * Note: For keyboard scrolling to work (that is, scrolling based on the page up/down keys),
+     * at least one child within the scrollable must be focusable. (This matches the behavior in
+     * Views.)
+     */
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun scrollable_verticalScroll_keyboardPageUpAndDown() {
+        var scrollAmount = 0f
+
+        val scrollableState = ScrollableState(
+            consumeScrollDelta = {
+                scrollAmount += it
+                it
+            }
+        )
+
+        rule.setContent {
+            Column(
+                Modifier
+                    .fillMaxWidth()
+                    .background(Color.Red)
+                    .scrollable(
+                        state = scrollableState,
+                        orientation = Orientation.Vertical
+                    )
+                    .padding(10.dp)
+            ) {
+                Box(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .testTag(scrollableBoxTag)
+                        .height(50.dp)
+                        .background(Color.Blue)
+                        // Required for keyboard scrolling (page up/down keys) to work.
+                        .focusable()
+                        .padding(10.dp)
+                )
+
+                Spacer(modifier = Modifier.size(10.dp))
+
+                for (i in 0 until 40) {
+                    val color = if (i % 2 == 0) {
+                        Color.Yellow
+                    } else {
+                        Color.Green
+                    }
+
+                    Box(
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .height(50.dp)
+                            .background(color)
+                            .padding(10.dp)
+                    )
+                    Spacer(modifier = Modifier.size(10.dp))
+                }
+            }
+        }
+
+        rule.onNodeWithTag(scrollableBoxTag).requestFocus()
+        rule.onNodeWithTag(scrollableBoxTag).performKeyInput {
+            pressKey(Key.PageDown)
+        }
+
+        rule.runOnIdle {
+            assertThat(scrollAmount).isLessThan(0f)
+        }
+
+        scrollAmount = 0f
+
+        rule.onNodeWithTag(scrollableBoxTag).performKeyInput {
+            pressKey(Key.PageUp)
+        }
+
+        rule.runOnIdle {
+            assertThat(scrollAmount).isGreaterThan(0f)
+        }
+    }
+
     @Test
     fun scrollable_verticalScroll_reversed() {
         var total = 0f
@@ -858,6 +1028,91 @@
         }
     }
 
+    /*
+     * Note: For keyboard scrolling to work (that is, scrolling based on the page up/down keys),
+     * at least one child within the scrollable must be focusable. (This matches the behavior in
+     * Views.)
+     */
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    fun scrollable_nestedScroll_childPartialConsumptionForKeyboardPageUpAndDown() {
+        var innerDrag = 0f
+        var outerDrag = 0f
+        val outerState = ScrollableState(
+            consumeScrollDelta = {
+                // Since the child has already consumed half, the parent will consume the rest.
+                outerDrag += it
+                it
+            }
+        )
+        val innerState = ScrollableState(
+            consumeScrollDelta = {
+                // Child consumes half, leaving the rest for the parent to consume.
+                innerDrag += it / 2
+                it / 2
+            }
+        )
+
+        rule.setContent {
+            Box {
+                Box(
+                    contentAlignment = Alignment.Center,
+                    modifier = Modifier
+                        .size(300.dp)
+                        .scrollable(
+                            state = outerState,
+                            orientation = Orientation.Horizontal
+                        )
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .size(300.dp)
+                            .scrollable(
+                                state = innerState,
+                                orientation = Orientation.Horizontal
+                            )
+                    ) {
+                        Box(
+                            modifier = Modifier
+                                .testTag(scrollableBoxTag)
+                                // Required for keyboard scrolling (page up/down keys) to work.
+                                .focusable()
+                                .size(300.dp)
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.onNodeWithTag(scrollableBoxTag).requestFocus()
+        rule.onNodeWithTag(scrollableBoxTag).performKeyInput {
+            pressKey(Key.PageDown)
+        }
+
+        rule.runOnIdle {
+            assertThat(outerDrag).isLessThan(0f)
+            assertThat(innerDrag).isLessThan(0f)
+            // Since child (inner) consumes half of the scroll, the parent (outer) consumes the
+            // remainder (which is half as well), so they will be equal.
+            assertThat(innerDrag).isEqualTo(outerDrag)
+        }
+
+        outerDrag = 0f
+        innerDrag = 0f
+
+        rule.onNodeWithTag(scrollableBoxTag).performKeyInput {
+            pressKey(Key.PageUp)
+        }
+
+        rule.runOnIdle {
+            assertThat(outerDrag).isGreaterThan(0f)
+            assertThat(innerDrag).isGreaterThan(0f)
+            // Since child (inner) consumes half of the scroll, the parent (outer) consumes the
+            // remainder (which is half as well), so they will be equal.
+            assertThat(innerDrag).isEqualTo(outerDrag)
+        }
+    }
+
     @Test
     fun scrollable_nestedFling() {
         var innerDrag = 0f
@@ -2674,16 +2929,13 @@
                             .firstOrNull()
 
                         if (currentEvent != null && !currentEvent.changedToUpIgnoreConsumed()) {
-                            if (currentEvent.historical.isEmpty()) {
-                                tracker.addPosition(
-                                    currentEvent.uptimeMillis,
-                                    currentEvent.position
-                                )
-                            } else {
-                                currentEvent.historical.fastForEach {
-                                    tracker.addPosition(it.uptimeMillis, it.position)
-                                }
+                            currentEvent.historical.fastForEach {
+                                tracker.addPosition(it.uptimeMillis, it.position)
                             }
+                            tracker.addPosition(
+                                currentEvent.uptimeMillis,
+                                currentEvent.position
+                            )
                         }
 
                         event = currentEvent
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/LazyGridSnapFlingBehaviorTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/LazyGridSnapFlingBehaviorTest.kt
index b756f09..6d04823 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/LazyGridSnapFlingBehaviorTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/LazyGridSnapFlingBehaviorTest.kt
@@ -401,9 +401,8 @@
         rule.mainClock.advanceTimeByFrame()
 
         // assert
-        val initialTargetOffset =
-            with(snapLayoutInfoProvider) { density.calculateApproachOffset(velocity) }
-        Truth.assertThat(scrollOffset.first { it != 0f }).isWithin(0.5f)
+        val initialTargetOffset = snapLayoutInfoProvider.calculateApproachOffset(velocity)
+        Truth.assertThat(scrollOffset[1]).isWithin(0.5f)
             .of(initialTargetOffset)
 
         // act: wait for remaining offset to grow instead of decay, this indicates the last
@@ -414,9 +413,7 @@
         }
 
         // assert: next calculated offset is the first value emitted by remainingScrollOffset
-        val finalRemainingOffset = with(snapLayoutInfoProvider) {
-            density.calculateSnappingOffset(10000f)
-        }
+        val finalRemainingOffset = snapLayoutInfoProvider.calculateSnappingOffset(10000f)
         Truth.assertThat(scrollOffset.last()).isWithin(0.5f)
             .of(finalRemainingOffset)
         rule.mainClock.autoAdvance = true
@@ -444,9 +441,11 @@
             flingBehavior = snapFlingBehavior
         ) {
             items(200) {
-                Box(modifier = Modifier
-                    .size(ItemSize)
-                    .background(Color.Yellow)) {
+                Box(
+                    modifier = Modifier
+                        .size(ItemSize)
+                        .background(Color.Yellow)
+                ) {
                     BasicText(text = it.toString())
                 }
             }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/LazyGridSnapLayoutInfoProviderTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/LazyGridSnapLayoutInfoProviderTest.kt
index b828a84..f0c6d32 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/LazyGridSnapLayoutInfoProviderTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/LazyGridSnapLayoutInfoProviderTest.kt
@@ -19,14 +19,10 @@
 import androidx.compose.animation.core.calculateTargetValue
 import androidx.compose.animation.splineBasedDecay
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
 import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.grid.BaseLazyGridTestWithOrientation
 import androidx.compose.foundation.lazy.grid.GridCells
@@ -35,14 +31,12 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.test.filters.LargeTest
 import kotlin.math.absoluteValue
-import kotlin.math.round
+import kotlin.math.floor
 import kotlin.math.sign
 import kotlin.test.assertEquals
 import org.junit.Test
@@ -58,118 +52,15 @@
     private val density: Density get() = rule.density
 
     @Test
-    fun snapStepSize_sameSizeItems_shouldBeAverageItemSize() {
-        var expectedItemSize = 0f
-        var actualItemSize = 0f
-
-        rule.setContent {
-            val density = LocalDensity.current
-            val state = rememberLazyGridState()
-            val layoutInfoProvider = remember(state) { createLayoutInfo(state) }.also {
-                actualItemSize = with(it) { density.calculateSnapStepSize() }
-            }
-            expectedItemSize = with(density) { FixedItemSize.toPx() }
-            MainLayout(
-                state = state,
-                layoutInfo = layoutInfoProvider,
-                items = 200,
-                itemSizeProvider = { FixedItemSize }
-            )
-        }
-
-        rule.runOnIdle {
-            assertEquals(round(expectedItemSize), round(actualItemSize))
-        }
-    }
-
-    @Test
-    fun snapStepSize_differentSizeItems_shouldBeAverageItemSizeOnReferenceIndex() {
-        var actualItemSize = 0f
-        var expectedItemSize = 0f
-        rule.setContent {
-            val density = LocalDensity.current
-            val state = rememberLazyGridState()
-            val layoutInfoProvider = remember(state) { createLayoutInfo(state) }.also {
-                actualItemSize = with(it) { density.calculateSnapStepSize() }
-            }
-            expectedItemSize = state.layoutInfo.visibleItemsInfo.filter {
-                if (vertical) {
-                    it.column == 0
-                } else {
-                    it.row == 0
-                }
-            }.map {
-                if (vertical) it.size.height else it.size.width
-            }.average().toFloat()
-
-            MainLayout(state, layoutInfoProvider, DynamicItemSizes.size, { DynamicItemSizes[it] })
-        }
-
-        rule.runOnIdle {
-            assertEquals(round(expectedItemSize), round(actualItemSize))
-        }
-    }
-
-    @Test
-    fun snapStepSize_withSpacers_shouldBeAverageItemSize() {
-        var snapStepSize = 0f
-        var actualItemSize = 0f
-        rule.setContent {
-            val density = LocalDensity.current
-            val state = rememberLazyGridState()
-            val layoutInfoProvider = remember(state) { createLayoutInfo(state) }.also {
-                snapStepSize = with(it) { density.calculateSnapStepSize() }
-            }
-
-            actualItemSize = with(density) { (FixedItemSize + FixedItemSize / 2).toPx() }
-
-            MainLayout(
-                state = state,
-                layoutInfo = layoutInfoProvider,
-                items = 200,
-                itemSizeProvider = { FixedItemSize }) {
-                if (vertical) {
-                    Column {
-                        Box(
-                            modifier = Modifier
-                                .size(FixedItemSize)
-                                .background(Color.Red)
-                        )
-                        Spacer(
-                            modifier = Modifier
-                                .size(FixedItemSize / 2)
-                                .background(Color.Yellow)
-                        )
-                    }
-                } else {
-                    Row {
-                        Box(
-                            modifier = Modifier
-                                .size(FixedItemSize)
-                                .background(Color.Red)
-                        )
-                        Spacer(
-                            modifier = Modifier
-                                .size(FixedItemSize / 2)
-                                .background(Color.Yellow)
-                        )
-                    }
-                }
-            }
-        }
-
-        rule.runOnIdle {
-            assertEquals(round(actualItemSize), round(snapStepSize))
-        }
-    }
-
-    @Test
     fun calculateApproachOffset_highVelocity_approachOffsetIsEqualToDecayMinusItemSize() {
         lateinit var layoutInfoProvider: SnapLayoutInfoProvider
         val decay = splineBasedDecay<Float>(rule.density)
         fun calculateTargetOffset(velocity: Float): Float {
-            val offset = decay.calculateTargetValue(0f, velocity).absoluteValue
-            return (offset - with(density) { 200.dp.toPx() }).coerceAtLeast(0f) * velocity.sign
+            val offset = decay.calculateTargetValue(0f, velocity)
+            val itemSize = with(density) { 200.dp.toPx() }
+            val estimatedNumberOfItemsInDecay = floor(offset.absoluteValue / itemSize)
+            val approachOffset = estimatedNumberOfItemsInDecay * itemSize - itemSize
+            return approachOffset.coerceAtLeast(0f) * velocity.sign
         }
         rule.setContent {
             val state = rememberLazyGridState()
@@ -187,11 +78,11 @@
 
         rule.runOnIdle {
             assertEquals(
-                with(layoutInfoProvider) { density.calculateApproachOffset(10000f) },
+                layoutInfoProvider.calculateApproachOffset(10000f),
                 calculateTargetOffset(10000f)
             )
             assertEquals(
-                with(layoutInfoProvider) { density.calculateApproachOffset(-10000f) },
+                layoutInfoProvider.calculateApproachOffset(-10000f),
                 calculateTargetOffset(-10000f)
             )
         }
@@ -216,11 +107,11 @@
 
         rule.runOnIdle {
             assertEquals(
-                with(layoutInfoProvider) { density.calculateApproachOffset(1000f) },
+                layoutInfoProvider.calculateApproachOffset(1000f),
                 0f
             )
             assertEquals(
-                with(layoutInfoProvider) { density.calculateApproachOffset(-1000f) },
+                layoutInfoProvider.calculateApproachOffset(-1000f),
                 0f
             )
         }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/LazyListSnapFlingBehaviorTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/LazyListSnapFlingBehaviorTest.kt
index e33c7cc..4a1a839 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/LazyListSnapFlingBehaviorTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/LazyListSnapFlingBehaviorTest.kt
@@ -396,8 +396,8 @@
 
         // assert
         val initialTargetOffset =
-            with(snapLayoutInfoProvider) { density.calculateApproachOffset(velocity) }
-        Truth.assertThat(scrollOffset.first { it != 0f }).isWithin(0.5f)
+            snapLayoutInfoProvider.calculateApproachOffset(velocity)
+        Truth.assertThat(scrollOffset[1]).isWithin(0.5f)
             .of(initialTargetOffset)
 
         // act: wait for remaining offset to grow instead of decay, this indicates the last
@@ -408,9 +408,7 @@
         }
 
         // assert: next calculated offset is the first value emitted by remainingScrollOffset
-        val finalRemainingOffset = with(snapLayoutInfoProvider) {
-            density.calculateSnappingOffset(10000f)
-        }
+        val finalRemainingOffset = snapLayoutInfoProvider.calculateSnappingOffset(10000f)
         Truth.assertThat(scrollOffset.last()).isWithin(0.5f)
             .of(finalRemainingOffset)
         rule.mainClock.autoAdvance = true
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/LazyListSnapLayoutInfoProviderTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/LazyListSnapLayoutInfoProviderTest.kt
index 259f657..a91fcb2 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/LazyListSnapLayoutInfoProviderTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/LazyListSnapLayoutInfoProviderTest.kt
@@ -23,7 +23,6 @@
 import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
 import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.list.BaseLazyListTestWithOrientation
@@ -31,13 +30,12 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.test.filters.LargeTest
 import kotlin.math.absoluteValue
-import kotlin.math.round
+import kotlin.math.floor
 import kotlin.math.sign
 import kotlin.test.assertEquals
 import org.junit.Test
@@ -54,86 +52,15 @@
         get() = rule.density
 
     @Test
-    fun snapStepSize_sameSizeItems_shouldBeAverageItemSize() {
-        var expectedItemSize = 0f
-        var actualItemSize = 0f
-
-        rule.setContent {
-            val density = LocalDensity.current
-            val state = rememberLazyListState()
-            val layoutInfoProvider = remember(state) { createLayoutInfo(state) }.also {
-                actualItemSize = with(it) { density.calculateSnapStepSize() }
-            }
-            expectedItemSize = with(density) { FixedItemSize.toPx() }
-            MainLayout(
-                state = state,
-                layoutInfo = layoutInfoProvider,
-                items = 200,
-                itemSizeProvider = { FixedItemSize }
-            )
-        }
-
-        rule.runOnIdle {
-            assertEquals(round(expectedItemSize), round(actualItemSize))
-        }
-    }
-
-    @Test
-    fun snapStepSize_differentSizeItems_shouldBeAverageItemSize() {
-        var actualItemSize = 0f
-        var expectedItemSize = 0f
-
-        rule.setContent {
-            val density = LocalDensity.current
-            val state = rememberLazyListState()
-            val layoutInfoProvider = remember(state) { createLayoutInfo(state) }.also {
-                actualItemSize = with(it) { density.calculateSnapStepSize() }
-            }
-            expectedItemSize = state.layoutInfo.visibleItemsInfo.map { it.size }.average().toFloat()
-
-            MainLayout(state, layoutInfoProvider, DynamicItemSizes.size, { DynamicItemSizes[it] })
-        }
-
-        rule.runOnIdle {
-            assertEquals(round(expectedItemSize), round(actualItemSize))
-        }
-    }
-
-    @Test
-    fun snapStepSize_withSpacers_shouldBeAverageItemSize() {
-        var snapStepSize = 0f
-        var actualItemSize = 0f
-        rule.setContent {
-            val density = LocalDensity.current
-            val state = rememberLazyListState()
-            val layoutInfoProvider = remember(state) { createLayoutInfo(state) }.also {
-                snapStepSize = with(it) { density.calculateSnapStepSize() }
-            }
-
-            actualItemSize = with(density) { (FixedItemSize + FixedItemSize / 2).toPx() }
-
-            MainLayout(
-                state = state,
-                layoutInfo = layoutInfoProvider,
-                items = 200,
-                itemSizeProvider = { FixedItemSize }) {
-                Box(modifier = Modifier.size(FixedItemSize))
-                Spacer(modifier = Modifier.size(FixedItemSize / 2))
-            }
-        }
-
-        rule.runOnIdle {
-            assertEquals(round(actualItemSize), round(snapStepSize))
-        }
-    }
-
-    @Test
     fun calculateApproachOffset_highVelocity_approachOffsetIsEqualToDecayMinusItemSize() {
         lateinit var layoutInfoProvider: SnapLayoutInfoProvider
         val decay = splineBasedDecay<Float>(rule.density)
         fun calculateTargetOffset(velocity: Float): Float {
-            val offset = decay.calculateTargetValue(0f, velocity).absoluteValue
-            return (offset - with(density) { 200.dp.toPx() }).coerceAtLeast(0f) * velocity.sign
+            val offset = decay.calculateTargetValue(0f, velocity)
+            val itemSize = with(density) { 200.dp.toPx() }
+            val estimatedNumberOfItemsInDecay = floor(offset.absoluteValue / itemSize)
+            val approachOffset = estimatedNumberOfItemsInDecay * itemSize - itemSize
+            return approachOffset.coerceAtLeast(0f) * velocity.sign
         }
         rule.setContent {
             val state = rememberLazyListState()
@@ -150,11 +77,11 @@
 
         rule.runOnIdle {
             assertEquals(
-                with(layoutInfoProvider) { density.calculateApproachOffset(10000f) },
+                layoutInfoProvider.calculateApproachOffset(10000f),
                 calculateTargetOffset(10000f)
             )
             assertEquals(
-                with(layoutInfoProvider) { density.calculateApproachOffset(-10000f) },
+                layoutInfoProvider.calculateApproachOffset(-10000f),
                 calculateTargetOffset(-10000f)
             )
         }
@@ -178,11 +105,11 @@
 
         rule.runOnIdle {
             assertEquals(
-                with(layoutInfoProvider) { density.calculateApproachOffset(1000f) },
+                layoutInfoProvider.calculateApproachOffset(1000f),
                 0f
             )
             assertEquals(
-                with(layoutInfoProvider) { density.calculateApproachOffset(-1000f) },
+                layoutInfoProvider.calculateApproachOffset(-1000f),
                 0f
             )
         }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/SnapFlingBehaviorTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/SnapFlingBehaviorTest.kt
index cc10f6f..38e7560 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/SnapFlingBehaviorTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/snapping/SnapFlingBehaviorTest.kt
@@ -239,21 +239,15 @@
     @Test
     fun findClosestOffset_noFlingDirection_shouldReturnAbsoluteDistance() {
         val testLayoutInfoProvider = TestLayoutInfoProvider()
-        val offset = with(testLayoutInfoProvider) {
-            density.calculateSnappingOffset(0f)
-        }
+        val offset = testLayoutInfoProvider.calculateSnappingOffset(0f)
         assertEquals(offset, MinOffset)
     }
 
     @Test
     fun findClosestOffset_flingDirection_shouldReturnCorrectBound() {
         val testLayoutInfoProvider = TestLayoutInfoProvider()
-        val forwardOffset = with(testLayoutInfoProvider) {
-            density.calculateSnappingOffset(1f)
-        }
-        val backwardOffset = with(testLayoutInfoProvider) {
-            density.calculateSnappingOffset(-1f)
-        }
+        val forwardOffset = testLayoutInfoProvider.calculateSnappingOffset(1f)
+        val backwardOffset = testLayoutInfoProvider.calculateSnappingOffset(-1f)
         assertEquals(forwardOffset, MaxOffset)
         assertEquals(backwardOffset, MinOffset)
     }
@@ -533,15 +527,11 @@
 ) : SnapLayoutInfoProvider {
     var calculateApproachOffsetCount = 0
 
-    override fun Density.calculateSnapStepSize(): Float {
-        return snapStep
-    }
-
-    override fun Density.calculateSnappingOffset(currentVelocity: Float): Float {
+    override fun calculateSnappingOffset(currentVelocity: Float): Float {
         return calculateFinalOffset(currentVelocity, minOffset, maxOffset)
     }
 
-    override fun Density.calculateApproachOffset(initialVelocity: Float): Float {
+    override fun calculateApproachOffset(initialVelocity: Float): Float {
         calculateApproachOffsetCount++
         return approachOffset
     }
@@ -565,7 +555,7 @@
             lowVelocityAnimationSpec = lowVelocityApproachSpec,
             highVelocityAnimationSpec = highVelocityApproachSpec,
             snapAnimationSpec = snapAnimationSpec,
-            density = density
+            shortSnapVelocityThreshold = with(density) { MinFlingVelocityDp.toPx() }
         )
     }
 }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/FontScalingScreenshotTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/FontScalingScreenshotTest.kt
index 4d27892..f83cf84 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/FontScalingScreenshotTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/FontScalingScreenshotTest.kt
@@ -37,6 +37,9 @@
 import androidx.compose.ui.text.font.FontStyle
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.rememberTextMeasurer
+import androidx.compose.ui.text.style.LineHeightStyle
+import androidx.compose.ui.text.style.LineHeightStyle.Alignment
+import androidx.compose.ui.text.style.LineHeightStyle.Trim
 import androidx.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.em
 import androidx.compose.ui.unit.sp
@@ -94,6 +97,38 @@
     }
 
     @Test
+    fun fontScaling1x_lineHeightStyleDoubleSp() {
+        AndroidFontScaleHelper.setSystemFontScale(1f, rule.activityRule.scenario)
+        rule.waitForIdle()
+
+        rule.setContent {
+            TestLayout(
+                lineHeight = 28.sp,
+                lineHeightStyle = LineHeightStyle(Alignment.Bottom, Trim.Both)
+            )
+        }
+        rule.onNodeWithTag(containerTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "fontScaling1x_lineHeightStyleDoubleSp")
+    }
+
+    @Test
+    fun fontScaling2x_lineHeightStyleDoubleSp() {
+        AndroidFontScaleHelper.setSystemFontScale(2f, rule.activityRule.scenario)
+        rule.waitForIdle()
+
+        rule.setContent {
+            TestLayout(
+                lineHeight = 28.sp,
+                lineHeightStyle = LineHeightStyle(Alignment.Bottom, Trim.Both)
+            )
+        }
+        rule.onNodeWithTag(containerTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "fontScaling2x_lineHeightStyleDoubleSp")
+    }
+
+    @Test
     fun fontScaling1x_lineHeightDoubleEm() {
         AndroidFontScaleHelper.setSystemFontScale(1f, rule.activityRule.scenario)
         rule.waitForIdle()
@@ -146,7 +181,10 @@
     }
 
     @Composable
-    private fun TestLayout(lineHeight: TextUnit) {
+    private fun TestLayout(
+        lineHeight: TextUnit,
+        lineHeightStyle: LineHeightStyle = LineHeightStyle.Default
+    ) {
         Column(
             modifier = Modifier.testTag(containerTag),
         ) {
@@ -177,7 +215,8 @@
                 style = TextStyle(
                     fontSize = 14.sp,
                     fontStyle = FontStyle.Italic,
-                    lineHeight = lineHeight
+                    lineHeight = lineHeight,
+                    lineHeightStyle = lineHeightStyle
                 )
             )
         }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/SelectionControllerTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/SelectionControllerTest.kt
index 225ffb2..905a598 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/SelectionControllerTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/modifiers/SelectionControllerTest.kt
@@ -70,9 +70,11 @@
             it.addRect(Rect(0f, 0f, pathSize, pathSize))
         }
 
+        val fixedSelectionFake = FixedSelectionFake(0, 1000, 200)
         val subject = SelectionController(
-            FixedSelectionFake(0, 1000, 200),
-            Color.White,
+            selectableId = fixedSelectionFake.nextSelectableId(),
+            selectionRegistrar = fixedSelectionFake,
+            backgroundSelectionColor = Color.White,
             params = FakeParams(
                 path, true
             )
@@ -104,9 +106,11 @@
             it.addRect(Rect(0f, 0f, pathSize, pathSize))
         }
 
+        val fixedSelectionFake = FixedSelectionFake(0, 1000, 200)
         val subject = SelectionController(
-            FixedSelectionFake(0, 1000, 200),
-            Color.White,
+            selectableId = fixedSelectionFake.nextSelectableId(),
+            selectionRegistrar = fixedSelectionFake,
+            backgroundSelectionColor = Color.White,
             params = FakeParams(
                 path, false
             )
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/LazyColumnMultiTextRegressionTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/LazyColumnMultiTextRegressionTest.kt
new file mode 100644
index 0000000..d78a009
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/LazyColumnMultiTextRegressionTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text.selection.gestures
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.selection.Selection
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.foundation.text.selection.fetchTextLayoutResult
+import androidx.compose.foundation.text.selection.gestures.util.longPress
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.testutils.TestViewConfiguration
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.TouchInjectionScope
+import androidx.compose.ui.test.junit4.StateRestorationTester
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.longClick
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipe
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.google.common.truth.Subject
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+class LazyColumnMultiTextRegressionTest {
+    @get:Rule
+    val rule = createComposeRule()
+    private val stateRestorationTester = StateRestorationTester(rule)
+
+    // regression - text going out of composition and then returning
+    // resulted in selection not working
+    @Test
+    fun whenTextScrollsOutOfCompositionAndThenBackIn_creatingSelectionStillPossible() = runTest {
+        scrollDown()
+        scrollUp()
+        createSelection(line = 0)
+        assertSelection().isNotNull()
+    }
+
+    @Test
+    fun whenSelectionScrollsOutOfCompositionAndThenBackIn_selectionRemains() = runTest {
+        createSelection(line = 0)
+        assertSelection().isNotNull()
+        val initialSelection = selection
+        scrollDown()
+        assertSelection().isEqualTo(initialSelection)
+        scrollUp()
+        assertSelection().isEqualTo(initialSelection)
+    }
+
+    // Copy currently doesn't work when the text leaves the view of a lazy layout
+    @Ignore("b/298067102")
+    @Test
+    fun whenTextScrollsOutOfLazyLayoutBounds_copyCorrectlySetsClipboard() = runTest {
+        resetClipboard()
+        createSelection(startLine = 0, endLine = 4)
+        assertSelection().isNotNull()
+        scrollDown()
+        performCopy()
+        assertClipboardTextEquals("01234")
+    }
+
+    // TODO(b/298067619)
+    //  When we support saving selection, this test should instead check that
+    //  the previous and current selection is the same.
+    //  Change test name to reflect this when implemented.
+    @Test
+    fun whenTextIsSavedRestored_clearsSelection() = runTest {
+        createSelection(line = 0)
+        assertSelection().isNotNull()
+        stateRestorationTester.emulateSavedInstanceStateRestore()
+        assertSelection().isNull()
+    }
+
+    private inner class TestScope(
+        private val pointerAreaTag: String,
+        private val selectionState: MutableState<Selection?>,
+        private val clipboardManager: ClipboardManager,
+    ) {
+        val initialText = "Initial text"
+        val selection get() = Snapshot.withoutReadObservation { selectionState.value }
+
+        fun createSelection(startLine: Int, endLine: Int) {
+            performTouchInput {
+                longPress(positionForLine(startLine))
+                moveTo(positionForLine(endLine))
+                up()
+            }
+        }
+
+        fun createSelection(line: Int) {
+            performTouchInput {
+                longClick(positionForLine(line))
+            }
+        }
+
+        private fun performTouchInput(block: TouchInjectionScope.() -> Unit) {
+            rule.onNodeWithTag(pointerAreaTag).performTouchInput(block)
+            rule.waitForIdle()
+        }
+
+        private fun positionForLine(lineNumber: Int): Offset {
+            val containerPosition = rule.onNodeWithTag(pointerAreaTag).fetchSemanticsNode()
+                .positionInRoot
+                .also { println(it) }
+
+            val textTag = lineNumber.toString()
+            val textPosition = rule.onNodeWithTag(textTag).fetchSemanticsNode()
+                .positionInRoot
+                .also { println(it) }
+
+            val textLayoutResult = rule.onNodeWithTag(textTag)
+                .fetchTextLayoutResult()
+
+            return textLayoutResult.getBoundingBox(0)
+                .translate(textPosition - containerPosition)
+                .center
+                .also { println(it) }
+        }
+
+        @OptIn(ExperimentalTestApi::class)
+        fun performCopy() {
+            rule.onNodeWithTag(pointerAreaTag).performKeyInput {
+                keyDown(Key.CtrlLeft)
+                keyDown(Key.C)
+                keyUp(Key.C)
+                keyUp(Key.CtrlLeft)
+            }
+            rule.waitForIdle()
+        }
+
+        fun resetClipboard() {
+            clipboardManager.setText(AnnotatedString(initialText))
+        }
+
+        fun assertClipboardTextEquals(text: String) {
+            val actualClipboardText = clipboardManager.getText()?.text
+            assertWithMessage("Clipboard contents was not changed.")
+                .that(actualClipboardText)
+                .isNotEqualTo(initialText)
+            assertWithMessage("""Clipboard set to incorrect content: "$actualClipboardText".""")
+                .that(actualClipboardText)
+                .isEqualTo(text)
+            resetClipboard()
+        }
+
+        fun assertSelection(): Subject = assertThat(selection)
+
+        fun scrollDown() {
+            performTouchInput {
+                swipe(bottomCenter - Offset(0f, 1f), topCenter + Offset(0f, 1f))
+            }
+        }
+
+        fun scrollUp() {
+            performTouchInput {
+                swipe(topCenter + Offset(0f, 1f), bottomCenter - Offset(0f, 1f))
+            }
+        }
+    }
+
+    private fun runTest(block: TestScope.() -> Unit) {
+        val tag = "tag"
+        val selection = mutableStateOf<Selection?>(null)
+        val testViewConfiguration = TestViewConfiguration(
+            minimumTouchTargetSize = DpSize.Zero
+        )
+        lateinit var clipboardManager: ClipboardManager
+        stateRestorationTester.setContent {
+            clipboardManager = LocalClipboardManager.current
+            CompositionLocalProvider(LocalViewConfiguration provides testViewConfiguration) {
+                SelectionContainer(
+                    selection = selection.value,
+                    onSelectionChange = { selection.value = it },
+                    modifier = Modifier
+                        .fillMaxSize()
+                        .wrapContentHeight()
+                ) {
+                    LazyColumn(
+                        modifier = Modifier
+                            .height(100.dp)
+                            .wrapContentHeight()
+                            .testTag(tag)
+                    ) {
+                        items(count = 20) {
+                            BasicText(
+                                text = it.toString(),
+                                style = TextStyle(fontSize = 15.sp, textAlign = TextAlign.Center),
+                                modifier = Modifier
+                                    .fillMaxWidth()
+                                    .testTag(it.toString())
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        val scope = TestScope(tag, selection, clipboardManager)
+        scope.resetClipboard()
+        scope.block()
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/MultiText2dSelectionGesturesTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/MultiText2dSelectionGesturesTest.kt
new file mode 100644
index 0000000..823454b
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/gestures/MultiText2dSelectionGesturesTest.kt
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text.selection.gestures
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.selection.Selection
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.foundation.text.selection.fetchTextLayoutResult
+import androidx.compose.foundation.text.selection.gestures.util.longPress
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.lerp
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.ResolvedTextDirection
+import androidx.compose.ui.unit.dp
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+internal class MultiText2dSelectionGesturesTest : AbstractSelectionGesturesTest() {
+
+    // 3 x 3 grid of texts
+    private val sideLength = 3
+
+    override val pointerAreaTag = "selectionContainer"
+    val line = "Test Text"
+    val text = "$line\n$line"
+
+    private val selection = mutableStateOf<Selection?>(null)
+
+    @Composable
+    override fun Content() {
+        SelectionContainer(
+            selection = selection.value,
+            onSelectionChange = { selection.value = it },
+            modifier = Modifier.testTag(pointerAreaTag)
+        ) {
+            Column(
+                modifier = Modifier.fillMaxSize(),
+                verticalArrangement = Arrangement.Center,
+                horizontalAlignment = Alignment.CenterHorizontally,
+            ) {
+                repeat(sideLength) { i ->
+                    Row(
+                        verticalAlignment = Alignment.CenterVertically,
+                        horizontalArrangement = Arrangement.Center,
+                    ) {
+                        repeat(sideLength) { j ->
+                            BasicText(
+                                text = text,
+                                style = TextStyle(
+                                    fontFamily = fontFamily,
+                                    fontSize = fontSize,
+                                ),
+                                modifier = Modifier
+                                    .padding(24.dp)
+                                    .testTag("${i * sideLength + j + 1}"),
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun dragUpperLeftText() {
+        dragTest(
+            dragPosition = characterPosition(1, 6),
+            selectableId = 1,
+            offset = 5,
+            crossed = true
+        )
+    }
+
+    @Test
+    fun dragUpperCenterText() {
+        dragTest(
+            dragPosition = characterPosition(2, 6),
+            selectableId = 2,
+            offset = 5,
+            crossed = true
+        )
+    }
+
+    @Test
+    fun dragUpperRightText() {
+        dragTest(
+            dragPosition = characterPosition(3, 6),
+            selectableId = 3,
+            offset = 6,
+            crossed = true
+        )
+    }
+
+    @Test
+    fun dragLeftText() {
+        dragTest(
+            dragPosition = characterPosition(4, 6),
+            selectableId = 4,
+            offset = 5,
+            crossed = true
+        )
+    }
+
+    @Test
+    fun dragSameText() {
+        dragTest(
+            dragPosition = characterPosition(5, 10),
+            selectableId = 5,
+            offset = 10,
+            crossed = false
+        )
+    }
+
+    @Test
+    fun dragRightText() {
+        dragTest(
+            dragPosition = characterPosition(6, 5),
+            selectableId = 6,
+            offset = 9,
+            crossed = false
+        )
+    }
+
+    @Test
+    fun dragLowerLeftText() {
+        dragTest(
+            dragPosition = characterPosition(7, 5),
+            selectableId = 7,
+            offset = 5,
+            crossed = false
+        )
+    }
+
+    @Test
+    fun dragLowerCenterText() {
+        dragTest(
+            dragPosition = characterPosition(8, 5),
+            selectableId = 8,
+            offset = 9,
+            crossed = false
+        )
+    }
+
+    @Test
+    fun dragLowerRightText() {
+        dragTest(
+            dragPosition = characterPosition(9, 5),
+            selectableId = 9,
+            offset = 9,
+            crossed = false
+        )
+    }
+
+    @Test
+    fun dragTopContainer() {
+        dragTest(
+            dragPosition = topEnd,
+            selectableId = 1,
+            offset = 0,
+            crossed = true
+        )
+    }
+
+    @Test
+    fun dragBetweenFirstAndSecondRow() {
+        dragTest(
+            dragPosition = betweenSelectables(2, 5),
+            selectableId = 4,
+            offset = 0,
+            crossed = true
+        )
+    }
+
+    @Test
+    fun dragBetweenSecondAndThirdRow() {
+        dragTest(
+            dragPosition = betweenSelectables(5, 8),
+            selectableId = 6,
+            offset = 19,
+            crossed = false
+        )
+    }
+
+    @Test
+    fun dragBottomContainer() {
+        dragTest(
+            dragPosition = bottomStart,
+            selectableId = 9,
+            offset = 19,
+            crossed = false
+        )
+    }
+
+    @Test
+    fun dragLeftContainer() {
+        dragTest(
+            // the offset should fall between lines,
+            // nudge it up so the position is on the upper line
+            dragPosition = centerStart.nudge(yDirection = VerticalDirection.UP),
+            selectableId = 4,
+            offset = 0,
+            crossed = true
+        )
+    }
+
+    @Test
+    fun dragBetweenLeftAndCenterTexts() {
+        dragTest(
+            dragPosition = betweenSelectables(4, 5).nudge(yDirection = VerticalDirection.UP),
+            selectableId = 5,
+            offset = 0,
+            crossed = true
+        )
+    }
+
+    @Test
+    fun dragBetweenCenterAndRightTexts() {
+        dragTest(
+            dragPosition = betweenSelectables(5, 6).nudge(yDirection = VerticalDirection.UP),
+            selectableId = 5,
+            offset = 9,
+            crossed = false
+        )
+    }
+
+    @Test
+    fun dragRightContainer() {
+        dragTest(
+            dragPosition = centerEnd.nudge(yDirection = VerticalDirection.UP),
+            selectableId = 6,
+            offset = 9,
+            crossed = false
+        )
+    }
+
+    private fun dragTest(
+        dragPosition: Offset,
+        selectableId: Int,
+        offset: Int,
+        crossed: Boolean,
+    ) {
+        performTouchGesture {
+            longPress(characterPosition(5, 6))
+        }
+
+        assertSelection(
+            startSelectableId = 5,
+            startOffset = 5,
+            endSelectableId = 5,
+            endOffset = 9,
+            handlesCrossed = false
+        )
+
+        touchDragTo(dragPosition)
+
+        assertSelection(
+            startSelectableId = 5,
+            startOffset = 5,
+            endSelectableId = selectableId,
+            endOffset = offset,
+            handlesCrossed = crossed
+        )
+
+        performTouchGesture {
+            up()
+        }
+
+        assertSelection(
+            startSelectableId = 5,
+            startOffset = 5,
+            endSelectableId = selectableId,
+            endOffset = offset,
+            handlesCrossed = crossed
+        )
+    }
+
+    // selectableIds are
+    //  1  2  3
+    //  4  5  6
+    //  7  8  9
+    private fun characterPosition(selectableId: Int, offset: Int): Offset {
+        val tag = "$selectableId"
+        val pointerAreaPosition =
+            rule.onNodeWithTag(pointerAreaTag).fetchSemanticsNode().positionInRoot
+        val nodePosition = rule.onNodeWithTag(tag).fetchSemanticsNode().positionInRoot
+        val textLayoutResult = rule.onNodeWithTag(tag).fetchTextLayoutResult()
+        return textLayoutResult.getBoundingBox(offset)
+            .translate(nodePosition - pointerAreaPosition)
+            .centerLeft
+            .nudge(HorizontalDirection.END)
+    }
+
+    private fun betweenSelectables(selectableId1: Int, selectableId2: Int): Offset {
+        val pointerAreaPosition =
+            rule.onNodeWithTag(pointerAreaTag).fetchSemanticsNode().positionInRoot
+
+        fun nodeCenter(selectableId: Int): Offset {
+            val tag = "$selectableId"
+            val node = rule.onNodeWithTag(tag).fetchSemanticsNode()
+            return node.boundsInRoot.center - pointerAreaPosition
+        }
+
+        return lerp(nodeCenter(selectableId1), nodeCenter(selectableId2), 0.5f)
+    }
+
+    private fun assertSelection(
+        startSelectableId: Int,
+        startOffset: Int,
+        endSelectableId: Int,
+        endOffset: Int,
+        handlesCrossed: Boolean,
+    ) {
+        assertThat(selection.value)
+            .isEqualTo(
+                Selection(
+                    start = Selection.AnchorInfo(
+                        direction = ResolvedTextDirection.Ltr,
+                        offset = startOffset,
+                        selectableId = startSelectableId.toLong()
+                    ),
+                    end = Selection.AnchorInfo(
+                        direction = ResolvedTextDirection.Ltr,
+                        offset = endOffset,
+                        selectableId = endSelectableId.toLong()
+                    ),
+                    handlesCrossed = handlesCrossed,
+                )
+            )
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldCodepointTransformationTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldCodepointTransformationTest.kt
index 5988872..90f3277 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldCodepointTransformationTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldCodepointTransformationTest.kt
@@ -27,18 +27,31 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.Key
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performSemanticsAction
 import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.performTextInputSelection
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.test.withKeyDown
+import androidx.compose.ui.text.TextRange
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlin.test.fail
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalFoundationApi::class)
+@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 class TextFieldCodepointTransformationTest {
@@ -46,6 +59,9 @@
     @get:Rule
     val rule = createComposeRule()
 
+    @get:Rule
+    val sessionHandler = InputMethodInterceptorRule(rule)
+
     private val Tag = "BasicTextField2"
 
     @Test
@@ -209,10 +225,526 @@
         assertLayoutText("Hello\nWorld")
     }
 
-    // TODO: add more tests when selection is added
+    @Test
+    fun surrogateToNonSurrogate_singleCodepoint_isTransformed() {
+        val state = TextFieldState(SingleSurrogateCodepointString)
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithNonSurrogate
+            )
+        }
+
+        assertLayoutText(".")
+    }
+
+    @Test
+    fun surrogateToNonSurrogate_multipleCodepoints_areTransformed() {
+        val state = TextFieldState(SingleSurrogateCodepointString + SingleSurrogateCodepointString)
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithNonSurrogate
+            )
+        }
+
+        assertLayoutText("..")
+    }
+
+    @Test
+    fun surrogateToNonSurrogate_withNonSurrogates_areTransformed() {
+        val state = TextFieldState("a${SingleSurrogateCodepointString}b")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithNonSurrogate
+            )
+        }
+
+        assertLayoutText("...")
+    }
+
+    @Test
+    fun nonSurrogateToSurrogate_singleCodepoint_isTransformed() {
+        val state = TextFieldState("a")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithSurrogate
+            )
+        }
+
+        assertLayoutText(SingleSurrogateCodepointString)
+    }
+
+    @Test
+    fun nonSurrogateToSurrogate_multipleCodepoints_areTransformed() {
+        val state = TextFieldState("ab")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithSurrogate
+            )
+        }
+
+        assertLayoutText(SingleSurrogateCodepointString + SingleSurrogateCodepointString)
+    }
+
+    @Test
+    fun nonSurrogateToSurrogate_withNonSurrogates_areTransformed() {
+        val state = TextFieldState("abc")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = { i, codepoint ->
+                    if (i == 1) SurrogateCodepoint else codepoint
+                }
+            )
+        }
+
+        assertLayoutText("a${SingleSurrogateCodepointString}c")
+    }
+
+    @Test
+    fun surrogateToNonSurrogate_singleCodepoint_selectionIsMappedAroundCodepoint() {
+        val state = TextFieldState(SingleSurrogateCodepointString)
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithNonSurrogate
+            )
+        }
+
+        assertVisualTextLength(1)
+        state.assertSelectionMappings(
+            TextRange(0) to TextRange(0),
+            TextRange(0, 1) to TextRange(0, 2),
+            TextRange(1, 0) to TextRange(2, 0),
+            TextRange(1) to TextRange(2),
+        )
+    }
+
+    @Test
+    fun nonSurrogateToSurrogate_singleCodepoint_selectionIsMappedAroundCodepoint() {
+        val state = TextFieldState("a")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithSurrogate
+            )
+        }
+
+        assertVisualTextLength(2)
+        state.assertSelectionMappings(
+            TextRange(0) to TextRange(0),
+            TextRange(0, 1) to TextRange(0, 1),
+            TextRange(0, 2) to TextRange(0, 1),
+            TextRange(1, 0) to TextRange(1, 0),
+            TextRange(1) to TextRange(0, 1),
+            TextRange(1, 2) to TextRange(0, 1),
+            TextRange(2, 0) to TextRange(1, 0),
+            TextRange(2, 1) to TextRange(1, 0),
+            TextRange(2) to TextRange(1),
+        )
+    }
+
+    @Test
+    fun multipleCodepoints_selectionIsMappedAroundCodepoints() {
+        val state = TextFieldState("a${SingleSurrogateCodepointString}c")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = { i, codepoint ->
+                    when (codepoint) {
+                        'a'.code, 'c'.code -> SurrogateCodepoint
+                        SurrogateCodepoint -> 'b'.code
+                        else -> fail(
+                            "unrecognized codepoint at index $i: " +
+                                String(intArrayOf(codepoint), 0, 1)
+                        )
+                    }
+                }
+            )
+        }
+
+        assertVisualTextLength(5)
+        state.assertSelectionMappings(
+            TextRange(0) to TextRange(0),
+            TextRange(0, 1) to TextRange(0, 1),
+            TextRange(0, 2) to TextRange(0, 1),
+            TextRange(0, 3) to TextRange(0, 3),
+            TextRange(0, 4) to TextRange(0, 4),
+            TextRange(0, 5) to TextRange(0, 4),
+            TextRange(1, 0) to TextRange(1, 0),
+            TextRange(1) to TextRange(0, 1),
+            TextRange(1, 2) to TextRange(0, 1),
+            TextRange(1, 3) to TextRange(0, 3),
+            TextRange(1, 4) to TextRange(0, 4),
+            TextRange(1, 5) to TextRange(0, 4),
+            TextRange(2, 0) to TextRange(1, 0),
+            TextRange(2, 1) to TextRange(1, 0),
+            TextRange(2) to TextRange(1),
+            TextRange(2, 3) to TextRange(1, 3),
+            TextRange(2, 4) to TextRange(1, 4),
+            TextRange(2, 5) to TextRange(1, 4),
+            TextRange(3, 0) to TextRange(3, 0),
+            TextRange(3, 1) to TextRange(3, 0),
+            TextRange(3, 2) to TextRange(3, 1),
+            TextRange(3) to TextRange(3),
+            TextRange(3, 4) to TextRange(3, 4),
+            TextRange(3, 5) to TextRange(3, 4),
+            TextRange(4, 0) to TextRange(4, 0),
+            TextRange(4, 1) to TextRange(4, 0),
+            TextRange(4, 2) to TextRange(4, 1),
+            TextRange(4, 3) to TextRange(4, 3),
+            TextRange(4) to TextRange(3, 4),
+            TextRange(4, 5) to TextRange(3, 4),
+        )
+    }
+
+    @Test
+    fun cursorTraversal_withArrowKeys() {
+        val state = TextFieldState("a${SingleSurrogateCodepointString}c")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = { i, codepoint ->
+                    when (codepoint) {
+                        'a'.code -> SurrogateCodepoint
+                        SurrogateCodepoint -> 'b'.code
+                        'c'.code -> SurrogateCodepoint
+                        else -> fail(
+                            "unrecognized codepoint at index $i: " +
+                                String(intArrayOf(codepoint), 0, 1)
+                        )
+                    }
+                }
+            )
+        }
+
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
+
+        listOf(0, 1, 3, 4).forEachIndexed { i, expectedCursor ->
+            rule.runOnIdle {
+                assertWithMessage("After pressing right arrow $i times")
+                    .that(state.text.selectionInChars).isEqualTo(TextRange(expectedCursor))
+            }
+            rule.onNodeWithTag(Tag).performKeyInput {
+                pressKey(Key.DirectionRight)
+            }
+        }
+    }
+
+    @Test
+    fun expandSelectionForward_withArrowKeys() {
+        val state = TextFieldState("a${SingleSurrogateCodepointString}c")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = { i, codepoint ->
+                    when (codepoint) {
+                        'a'.code -> SurrogateCodepoint
+                        SurrogateCodepoint -> 'b'.code
+                        'c'.code -> SurrogateCodepoint
+                        else -> fail(
+                            "unrecognized codepoint at index $i: " +
+                                String(intArrayOf(codepoint), 0, 1)
+                        )
+                    }
+                }
+            )
+        }
+
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
+
+        listOf(
+            TextRange(0),
+            TextRange(0, 1),
+            TextRange(0, 3),
+            TextRange(0, 4)
+        ).forEachIndexed { i, expectedSelection ->
+            rule.runOnIdle {
+                assertWithMessage("After pressing shift+right arrow $i times")
+                    .that(state.text.selectionInChars).isEqualTo(expectedSelection)
+            }
+            rule.onNodeWithTag(Tag).performKeyInput {
+                withKeyDown(Key.ShiftLeft) {
+                    pressKey(Key.DirectionRight)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun expandSelectionBackward_withArrowKeys() {
+        val state = TextFieldState("a${SingleSurrogateCodepointString}c")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = { i, codepoint ->
+                    when (codepoint) {
+                        'a'.code -> SurrogateCodepoint
+                        SurrogateCodepoint -> 'b'.code
+                        'c'.code -> SurrogateCodepoint
+                        else -> fail(
+                            "unrecognized codepoint at index $i: " +
+                                String(intArrayOf(codepoint), 0, 1)
+                        )
+                    }
+                }
+            )
+        }
+
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(4))
+
+        listOf(
+            TextRange(4),
+            TextRange(4, 3),
+            TextRange(4, 1),
+            TextRange(4, 0)
+        ).forEachIndexed { i, expectedSelection ->
+            rule.runOnIdle {
+                assertWithMessage("After pressing shift+left arrow $i times")
+                    .that(state.text.selectionInChars).isEqualTo(expectedSelection)
+            }
+            rule.onNodeWithTag(Tag).performKeyInput {
+                withKeyDown(Key.ShiftLeft) {
+                    pressKey(Key.DirectionLeft)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun insertNonSurrogates_intoSurrogateMask_fromKeyEvents() {
+        val state = TextFieldState("a$SingleSurrogateCodepointString")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithSurrogate
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
+
+        rule.onNodeWithTag(Tag).performKeyInput {
+            pressKey(Key.X)
+            pressKey(Key.DirectionRight)
+            pressKey(Key.Y)
+            pressKey(Key.DirectionRight)
+            pressKey(Key.Z)
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
+        }
+        assertVisualTextLength(10)
+    }
+
+    @Test
+    fun insertNonSurrogates_intoNonSurrogateMask_fromKeyEvents() {
+        val state = TextFieldState("a$SingleSurrogateCodepointString")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithNonSurrogate
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
+
+        rule.onNodeWithTag(Tag).performKeyInput {
+            pressKey(Key.X)
+            pressKey(Key.DirectionRight)
+            pressKey(Key.Y)
+            pressKey(Key.DirectionRight)
+            pressKey(Key.Z)
+        }
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
+        }
+        assertVisualTextLength(5)
+    }
+
+    @Test
+    fun insertText_intoSurrogateMask_fromSemantics() {
+        val state = TextFieldState("a$SingleSurrogateCodepointString")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithSurrogate
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
+
+        // Use semantics to actually input the text, just use key events to move the cursor.
+        rule.onNodeWithTag(Tag).performTextInput("x")
+        pressKey(Key.DirectionRight)
+        rule.onNodeWithTag(Tag).performTextInput("y")
+        pressKey(Key.DirectionRight)
+        rule.onNodeWithTag(Tag).performTextInput("z")
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
+        }
+        assertVisualTextLength(10)
+    }
+
+    @Test
+    fun insertNonSurrogates_intoNonSurrogateMask_fromSemantics() {
+        val state = TextFieldState("a$SingleSurrogateCodepointString")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithNonSurrogate
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+        rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(0))
+
+        // Use semantics to actually input the text, just use key events to move the cursor.
+        rule.onNodeWithTag(Tag).performTextInput("x")
+        pressKey(Key.DirectionRight)
+        rule.onNodeWithTag(Tag).performTextInput("y")
+        pressKey(Key.DirectionRight)
+        rule.onNodeWithTag(Tag).performTextInput("z")
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
+        }
+        assertVisualTextLength(5)
+    }
+
+    @Test
+    fun insertText_intoSurrogateMask_fromIme() {
+        val state = TextFieldState("a$SingleSurrogateCodepointString")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithSurrogate
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+        sessionHandler.withInputConnection {
+            beginBatchEdit()
+            finishComposingText()
+            setSelection(0, 0)
+            endBatchEdit()
+        }
+
+        sessionHandler.withInputConnection { commitText("x", 1) }
+        pressKey(Key.DirectionRight)
+        sessionHandler.withInputConnection { commitText("y", 1) }
+        pressKey(Key.DirectionRight)
+        sessionHandler.withInputConnection { commitText("z", 1) }
+        pressKey(Key.DirectionRight)
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
+        }
+        assertVisualTextLength(10)
+    }
+
+    @Test
+    fun insertText_intoNonSurrogateMask_fromIme() {
+        val state = TextFieldState("a$SingleSurrogateCodepointString")
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                codepointTransformation = MaskWithNonSurrogate
+            )
+        }
+        rule.onNodeWithTag(Tag).requestFocus()
+        sessionHandler.withInputConnection {
+            beginBatchEdit()
+            finishComposingText()
+            setSelection(0, 0)
+            endBatchEdit()
+        }
+
+        sessionHandler.withInputConnection { commitText("x", 1) }
+        pressKey(Key.DirectionRight)
+        sessionHandler.withInputConnection { commitText("y", 1) }
+        pressKey(Key.DirectionRight)
+        sessionHandler.withInputConnection { commitText("z", 1) }
+        pressKey(Key.DirectionRight)
+
+        rule.runOnIdle {
+            assertThat(state.text.toString()).isEqualTo("xay${SingleSurrogateCodepointString}z")
+        }
+        assertVisualTextLength(5)
+    }
 
     private fun assertLayoutText(text: String) {
         assertThat(rule.onNodeWithTag(Tag).fetchTextLayoutResult().layoutInput.text.text)
             .isEqualTo(text)
     }
+
+    private fun assertVisualTextLength(expectedLength: Int) {
+        assertThat(rule.onNodeWithTag(Tag).fetchTextLayoutResult().layoutInput.text.text)
+            .hasLength(expectedLength)
+    }
+
+    private fun TextFieldState.assertSelectionMappings(
+        vararg mappings: Pair<TextRange, TextRange>
+    ) {
+        mappings.forEach { (write, expected) ->
+            val existingSelection = rule.onNodeWithTag(Tag)
+                .fetchSemanticsNode().config[SemanticsProperties.TextSelectionRange]
+            // Setting the selection to the current selection will return false.
+            if (existingSelection != write) {
+                assertWithMessage("Expected to be able to select $write")
+                    .that(performSelectionOnVisualText(write)).isTrue()
+                rule.runOnIdle {
+                    assertWithMessage("Visual selection $write to mapped")
+                        .that(text.selectionInChars).isEqualTo(expected)
+                }
+            }
+        }
+    }
+
+    private fun performSelectionOnVisualText(selection: TextRange): Boolean {
+        rule.onNodeWithTag(Tag).requestFocus()
+        var actionSucceeded = false
+        rule.onNodeWithTag(Tag).performSemanticsAction(SemanticsActions.SetSelection) {
+            actionSucceeded = it(selection.start, selection.end, /* relativeToOriginal= */ false)
+        }
+        return actionSucceeded
+    }
+
+    private fun pressKey(key: Key) {
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(key) }
+    }
+
+    private companion object {
+        /** This is "𐐷", a surrogate codepoint. */
+        val SurrogateCodepoint = Character.toCodePoint('\uD801', '\uDC37')
+        const val SingleSurrogateCodepointString = "\uD801\uDC37"
+
+        val MaskWithSurrogate = CodepointTransformation { _, _ -> SurrogateCodepoint }
+        val MaskWithNonSurrogate = CodepointTransformation { _, _ -> '.'.code }
+    }
 }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputSessionTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputSessionTest.kt
index fc82f19..695ae0e 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputSessionTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputSessionTest.kt
@@ -219,9 +219,12 @@
         imeOptions: ImeOptions = ImeOptions.Default,
         onImeAction: (ImeAction) -> Unit = {}
     ): Nothing = platformSpecificTextInputSession(
-        state = state,
+        state = TransformedTextFieldState(
+            textFieldState = state,
+            inputTransformation = null,
+            codepointTransformation = null
+        ),
         imeOptions = imeOptions,
-        filter = null,
         onImeAction = onImeAction
     )
 
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldLayoutStateCacheTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldLayoutStateCacheTest.kt
index 68e4000..4d8908c 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldLayoutStateCacheTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldLayoutStateCacheTest.kt
@@ -57,7 +57,11 @@
     val rule = createComposeRule()
 
     private var textFieldState = TextFieldState()
-    private var codepointTransformation: CodepointTransformation? = null
+    private var transformedTextFieldState = TransformedTextFieldState(
+        textFieldState,
+        inputTransformation = null,
+        codepointTransformation = null
+    )
     private var textStyle = TextStyle()
     private var singleLine = false
     private var softWrap = false
@@ -120,7 +124,12 @@
     @Test
     fun updateNonMeasureInputs_invalidatesSnapshot_whenCodepointTransformationChanged() {
         assertInvalidationsOnChange(1) {
-            codepointTransformation = CodepointTransformation { _, codepoint -> codepoint }
+            val codepointTransformation = CodepointTransformation { _, codepoint -> codepoint }
+            transformedTextFieldState = TransformedTextFieldState(
+                textFieldState,
+                inputTransformation = null,
+                codepointTransformation
+            )
             updateNonMeasureInputs()
         }
     }
@@ -264,12 +273,16 @@
     fun invalidatesAllReaders_whenTransformationDependenciesChanged_producingSameVisualText() {
         var transformationState by mutableStateOf(1)
         var transformationInvocations = 0
-        codepointTransformation = CodepointTransformation { _, codepoint ->
+        val codepointTransformation = CodepointTransformation { _, codepoint ->
             transformationInvocations++
             @Suppress("UNUSED_EXPRESSION")
             transformationState
             codepoint + 1
         }
+        transformedTextFieldState = TransformedTextFieldState(
+            textFieldState, inputTransformation = null,
+            codepointTransformation
+        )
         // Transformation isn't applied if there's no text. Keep this at 1 char to make the math
         // simpler.
         textFieldState.setTextAndPlaceCursorAtEnd("h")
@@ -295,14 +308,14 @@
                 primaryInvalidations++
                 assertVisualText()
             }) { assertVisualText() }
-            assertThat(transformationInvocations).isEqualTo(2)
+            assertThat(transformationInvocations).isEqualTo(1)
 
             // This should be a full cache hit.
             secondaryObserver.observeReads(Unit, onValueChangedForScope = {
                 secondaryInvalidations++
                 assertVisualText()
             }) { assertVisualText() }
-            assertThat(transformationInvocations).isEqualTo(3)
+            assertThat(transformationInvocations).isEqualTo(1)
 
             // Invalidate the transformation.
             transformationState++
@@ -312,7 +325,7 @@
         }
 
         assertVisualText()
-        assertThat(transformationInvocations).isEqualTo(6)
+        assertThat(transformationInvocations).isEqualTo(2)
         assertThat(primaryInvalidations).isEqualTo(1)
         assertThat(secondaryInvalidations).isEqualTo(1)
     }
@@ -321,10 +334,15 @@
     fun invalidatesAllReaders_whenTransformationDependenciesChanged_producingNewVisualText() {
         var transformationState by mutableStateOf(1)
         var transformationInvocations = 0
-        codepointTransformation = CodepointTransformation { _, codepoint ->
+        val codepointTransformation = CodepointTransformation { _, codepoint ->
             transformationInvocations++
             codepoint + transformationState
         }
+        transformedTextFieldState = TransformedTextFieldState(
+            textFieldState,
+            inputTransformation = null,
+            codepointTransformation
+        )
         // Transformation isn't applied if there's no text. Keep this at 1 char to make the math
         // simpler.
         textFieldState.setTextAndPlaceCursorAtEnd("h")
@@ -350,14 +368,14 @@
                 primaryInvalidations++
                 assertVisualText()
             }) { assertVisualText() }
-            assertThat(transformationInvocations).isEqualTo(2)
+            assertThat(transformationInvocations).isEqualTo(1)
 
             // This should be a full cache hit.
             secondaryObserver.observeReads(Unit, onValueChangedForScope = {
                 secondaryInvalidations++
                 assertVisualText()
             }) { assertVisualText() }
-            assertThat(transformationInvocations).isEqualTo(3)
+            assertThat(transformationInvocations).isEqualTo(1)
 
             // Invalidate the transformation.
             expectedVisualText = "j"
@@ -369,7 +387,7 @@
 
         assertVisualText()
         // Two more reads means two more applications of the transformation.
-        assertThat(transformationInvocations).isEqualTo(6)
+        assertThat(transformationInvocations).isEqualTo(2)
         assertThat(primaryInvalidations).isEqualTo(1)
         assertThat(secondaryInvalidations).isEqualTo(1)
     }
@@ -413,10 +431,20 @@
     @Test
     fun value_returnsNewLayout_whenCodepointTransformationInstanceChangedWithDifferentOutput() {
         textFieldState.setTextAndPlaceCursorAtEnd("h")
-        codepointTransformation = CodepointTransformation { _, codepoint -> codepoint }
+        var codepointTransformation = CodepointTransformation { _, codepoint -> codepoint }
+        transformedTextFieldState = TransformedTextFieldState(
+            textFieldState,
+            inputTransformation = null,
+            codepointTransformation
+        )
         assertLayoutChange(
             change = {
                 codepointTransformation = CodepointTransformation { _, codepoint -> codepoint + 1 }
+                transformedTextFieldState = TransformedTextFieldState(
+                    textFieldState,
+                    inputTransformation = null,
+                    codepointTransformation
+                )
                 updateNonMeasureInputs()
             }
         ) { old, new ->
@@ -428,10 +456,20 @@
     @Test
     fun value_returnsCachedLayout_whenCodepointTransformationInstanceChangedWithSameOutput() {
         textFieldState.setTextAndPlaceCursorAtEnd("h")
-        codepointTransformation = CodepointTransformation { _, codepoint -> codepoint }
+        var codepointTransformation = CodepointTransformation { _, codepoint -> codepoint }
+        transformedTextFieldState = TransformedTextFieldState(
+            textFieldState,
+            inputTransformation = null,
+            codepointTransformation
+        )
         assertLayoutChange(
             change = {
                 codepointTransformation = CodepointTransformation { _, codepoint -> codepoint }
+                transformedTextFieldState = TransformedTextFieldState(
+                    textFieldState,
+                    inputTransformation = null,
+                    codepointTransformation
+                )
                 updateNonMeasureInputs()
             }
         ) { old, new ->
@@ -706,8 +744,7 @@
 
     private fun updateNonMeasureInputs() {
         cache.updateNonMeasureInputs(
-            textFieldState = textFieldState,
-            codepointTransformation = codepointTransformation,
+            textFieldState = transformedTextFieldState,
             textStyle = textStyle,
             singleLine = singleLine,
             softWrap = softWrap
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/GraphicsSurface.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/GraphicsSurface.kt
new file mode 100644
index 0000000..17a6823
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/GraphicsSurface.kt
@@ -0,0 +1,438 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation
+
+import android.graphics.PixelFormat
+import android.graphics.SurfaceTexture
+import android.view.Surface
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+import android.view.TextureView
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.viewinterop.AndroidView
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.launch
+
+/**
+ * [SurfaceScope] is a scoped environment provided by [GraphicsSurface] and
+ * [EmbeddedGraphicsSurface] to handle [Surface] lifecycle events.
+ *
+ * @sample androidx.compose.foundation.samples.GraphicsSurfaceColors
+ */
+interface SurfaceScope {
+    /**
+     * Invokes [onChanged] when the surface's geometry (width and height) changes.
+     * Always invoked on the main thread.
+     */
+    @Suppress("PrimitiveInLambda")
+    fun Surface.onChanged(onChanged: Surface.(width: Int, height: Int) -> Unit)
+
+    /**
+     * Invokes [onDestroyed] when the surface is destroyed. All rendering into
+     * the surface should stop immediately after [onDestroyed] is invoked.
+     * Always invoked on the main thread.
+     */
+    fun Surface.onDestroyed(onDestroyed: Surface.() -> Unit)
+}
+
+/**
+ * [SurfaceCoroutineScope] is a scoped environment provided by
+ * [GraphicsSurface] and [EmbeddedGraphicsSurface] when a new [Surface] is
+ * created. This environment is a coroutine scope that also provides access to
+ * a [SurfaceScope] environment which can itself be used to handle other [Surface]
+ * lifecycle events.
+ *
+ * @see SurfaceScope
+ * @see GraphicsSurfaceScope
+ *
+ * @sample androidx.compose.foundation.samples.GraphicsSurfaceColors
+ */
+interface SurfaceCoroutineScope : SurfaceScope, CoroutineScope
+
+/**
+ * [GraphicsSurfaceScope] is a scoped environment provided when a [GraphicsSurface]
+ * or [EmbeddedGraphicsSurface] is first initialized. This environment can be
+ * used to register a lambda to invoke when a new [Surface] associated with the
+ * [GraphicsSurface]/[EmbeddedGraphicsSurface] is created.
+ */
+interface GraphicsSurfaceScope {
+    /**
+     * Invokes [onSurface] when a new [Surface] is created. The [onSurface] lambda
+     * is invoked on the main thread as part of a [SurfaceCoroutineScope] to provide
+     * a coroutine context.
+     *
+     * @param onSurface Callback invoked when a new [Surface] is created. The initial
+     *                  dimensions of the surface are provided.
+     */
+    @Suppress("PrimitiveInLambda")
+    fun onSurface(
+        onSurface: suspend SurfaceCoroutineScope.(surface: Surface, width: Int, height: Int) -> Unit
+    )
+}
+
+/**
+ * Base class for [GraphicsSurface] and [EmbeddedGraphicsSurface] state. This class
+ * provides methods to properly dispatch lifecycle events on [Surface] creation,
+ * change, and destruction. Surface creation is treated as a coroutine launch,
+ * using the specified [scope] as the parent. This scope must be the main thread scope.
+ */
+private abstract class BaseGraphicsSurfaceState(val scope: CoroutineScope) :
+    GraphicsSurfaceScope, SurfaceScope {
+
+    private var onSurface:
+        (suspend SurfaceCoroutineScope.(surface: Surface, width: Int, height: Int) -> Unit)? = null
+    private var onSurfaceChanged: (Surface.(width: Int, height: Int) -> Unit)? = null
+    private var onSurfaceDestroyed: (Surface.() -> Unit)? = null
+
+    private var job: Job? = null
+
+    override fun onSurface(
+        onSurface: suspend SurfaceCoroutineScope.(surface: Surface, width: Int, height: Int) -> Unit
+    ) {
+        this.onSurface = onSurface
+    }
+
+    override fun Surface.onChanged(onChanged: Surface.(width: Int, height: Int) -> Unit) {
+        onSurfaceChanged = onChanged
+    }
+
+    override fun Surface.onDestroyed(onDestroyed: Surface.() -> Unit) {
+        onSurfaceDestroyed = onDestroyed
+    }
+
+    /**
+     * Dispatch a surface creation event by launching a new coroutine in [scope].
+     * Any previous job from a previous surface creation dispatch is cancelled.
+     */
+    fun dispatchSurfaceCreated(surface: Surface, width: Int, height: Int) {
+        if (onSurface != null) {
+            job = scope.launch(start = CoroutineStart.UNDISPATCHED) {
+                job?.cancelAndJoin()
+                val receiver =
+                    object : SurfaceCoroutineScope, SurfaceScope by this@BaseGraphicsSurfaceState,
+                        CoroutineScope by this {}
+                onSurface?.invoke(receiver, surface, width, height)
+            }
+        }
+    }
+
+    /**
+     * Dispatch a surface change event, providing the surface's new width and height.
+     * Must be invoked from the main thread.
+     */
+    fun dispatchSurfaceChanged(surface: Surface, width: Int, height: Int) {
+        onSurfaceChanged?.invoke(surface, width, height)
+    }
+
+    /**
+     * Dispatch a surface destruction event. Any pending job from [dispatchSurfaceCreated]
+     * is cancelled before dispatching the event. Must be invoked from the main thread.
+     */
+    fun dispatchSurfaceDestroyed(surface: Surface) {
+        onSurfaceDestroyed?.invoke(surface)
+        job?.cancel()
+        job = null
+    }
+}
+
+private class GraphicsSurfaceState(scope: CoroutineScope) : BaseGraphicsSurfaceState(scope),
+    SurfaceHolder.Callback {
+
+    var lastWidth = -1
+    var lastHeight = -1
+
+    override fun surfaceCreated(holder: SurfaceHolder) {
+        val frame = holder.surfaceFrame
+        lastWidth = frame.width()
+        lastHeight = frame.height()
+
+        dispatchSurfaceCreated(holder.surface, lastWidth, lastHeight)
+    }
+
+    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+        if (lastWidth != width || lastHeight != height) {
+            lastWidth = width
+            lastHeight = height
+
+            dispatchSurfaceChanged(holder.surface, width, height)
+        }
+    }
+
+    override fun surfaceDestroyed(holder: SurfaceHolder) {
+        dispatchSurfaceDestroyed(holder.surface)
+    }
+}
+
+@Composable
+private fun rememberGraphicsSurfaceState(): GraphicsSurfaceState {
+    val scope = rememberCoroutineScope()
+    return remember { GraphicsSurfaceState(scope) }
+}
+
+@JvmInline
+value class GraphicsSurfaceZOrder private constructor(val zOrder: Int) {
+    companion object {
+        val Behind = GraphicsSurfaceZOrder(0)
+        val MediaOverlay = GraphicsSurfaceZOrder(1)
+        val OnTop = GraphicsSurfaceZOrder(2)
+    }
+}
+
+/**
+ * Provides a dedicated drawing [Surface] as a separate layer positioned by default behind
+ * the window holding the [GraphicsSurface] composable. Because [GraphicsSurface] uses
+ * a separate window layer, graphics composition is handled by the system compositor which
+ * can bypass the GPU and provide better performance and power usage characteristics compared
+ * to [EmbeddedGraphicsSurface]. It is therefore recommended to use [GraphicsSurface]
+ * whenever possible.
+ *
+ * The z-ordering of the surface can be controlled using the [zOrder] parameter:
+ *
+ * - [GraphicsSurfaceZOrder.Behind]: positions the surface behind the window
+ * - [GraphicsSurfaceZOrder.MediaOverlay]: positions the surface behind the window but
+ *   above other [GraphicsSurfaceZOrder.Behind] surfaces
+ * - [GraphicsSurfaceZOrder.OnTop]: positions the surface above the window
+ *
+ * The drawing surface is opaque by default, which can be controlled with the [isOpaque]
+ * parameter. When the surface is transparent, you may need to change the z-order to
+ * see something behind the surface.
+ *
+ * To start rendering, the caller must first acquire the [Surface] when it's created.
+ * This is achieved by providing the [onInit] lambda, which allows the caller to
+ * register an appropriate [GraphicsSurfaceScope.onSurface] callback. The [onInit]
+ * lambda can also be used to initialize/cache resources needed once a surface is
+ * available.
+ *
+ * After acquiring a surface, the caller can start rendering into it. Rendering into a
+ * surface can be done from any thread.
+ *
+ * It is recommended to register the [SurfaceScope.onChanged] and [SurfaceScope.onDestroyed]
+ * callbacks to properly handle the lifecycle of the surface and react to dimension
+ * changes. You must ensure that the rendering thread stops interacting with the surface
+ * when the [SurfaceScope.onDestroyed] callback is invoked.
+ *
+ * If a [surfaceSize] is specified (set to non-[IntSize.Zero]), the surface will use
+ * the specified size instead of the layout size of this composable. The surface will
+ * be stretched at render time to fit the layout size. This can be used for instance to
+ * render at a lower resolution for performance reasons.
+ *
+ * @param modifier Modifier to be applied to the [GraphicsSurface]
+ * @param isOpaque Whether the managed surface should be opaque or transparent.
+ * @param zOrder Sets the z-order of the surface relative to its parent window.
+ * @param surfaceSize Sets the surface size independently of the layout size of
+ *                    this [GraphicsSurface]. If set to [IntSize.Zero], the surface
+ *                    size will be equal to the [GraphicsSurface] layout size.
+ * @param isSecure Control whether the surface view's content should be treated as
+ *                 secure, preventing it from appearing in screenshots or from being
+ *                 viewed on non-secure displays.
+ * @param onInit Lambda invoked on first composition. This lambda can be used to
+ *               declare a [GraphicsSurfaceScope.onSurface] callback that will be
+ *               invoked when a surface is available.
+ *
+ * @sample androidx.compose.foundation.samples.GraphicsSurfaceColors
+ */
+@Composable
+fun GraphicsSurface(
+    modifier: Modifier = Modifier,
+    isOpaque: Boolean = true,
+    zOrder: GraphicsSurfaceZOrder = GraphicsSurfaceZOrder.Behind,
+    surfaceSize: IntSize = IntSize.Zero,
+    isSecure: Boolean = false,
+    onInit: GraphicsSurfaceScope.() -> Unit
+) {
+    val state = rememberGraphicsSurfaceState()
+
+    AndroidView(
+        factory = { context ->
+            SurfaceView(context).apply {
+                state.onInit()
+                holder.addCallback(state)
+            }
+        },
+        modifier = modifier,
+        onReset = { },
+        update = { view ->
+            if (surfaceSize != IntSize.Zero) {
+                view.holder.setFixedSize(surfaceSize.width, surfaceSize.height)
+            } else {
+                view.holder.setSizeFromLayout()
+            }
+
+            view.holder.setFormat(
+                if (isOpaque) {
+                    PixelFormat.OPAQUE
+                } else {
+                    PixelFormat.TRANSLUCENT
+                }
+            )
+
+            when (zOrder) {
+                GraphicsSurfaceZOrder.Behind -> view.setZOrderOnTop(false)
+                GraphicsSurfaceZOrder.MediaOverlay -> view.setZOrderMediaOverlay(true)
+                GraphicsSurfaceZOrder.OnTop -> view.setZOrderOnTop(true)
+            }
+
+            view.setSecure(isSecure)
+        }
+    )
+}
+
+private class EmbeddedGraphicsSurfaceState(scope: CoroutineScope) : BaseGraphicsSurfaceState(scope),
+    TextureView.SurfaceTextureListener {
+
+    var surfaceSize = IntSize.Zero
+
+    private var surfaceTextureSurface: Surface? = null
+
+    override fun onSurfaceTextureAvailable(
+        surfaceTexture: SurfaceTexture,
+        width: Int,
+        height: Int
+    ) {
+        var w = width
+        var h = height
+
+        if (surfaceSize != IntSize.Zero) {
+            w = surfaceSize.width
+            h = surfaceSize.height
+            surfaceTexture.setDefaultBufferSize(w, h)
+        }
+
+        val surface = Surface(surfaceTexture)
+        surfaceTextureSurface = surface
+
+        dispatchSurfaceCreated(surface, w, h)
+    }
+
+    override fun onSurfaceTextureSizeChanged(
+        surfaceTexture: SurfaceTexture,
+        width: Int,
+        height: Int
+    ) {
+        var w = width
+        var h = height
+
+        if (surfaceSize != IntSize.Zero) {
+            w = surfaceSize.width
+            h = surfaceSize.height
+            surfaceTexture.setDefaultBufferSize(w, h)
+        }
+
+        dispatchSurfaceChanged(surfaceTextureSurface!!, w, h)
+    }
+
+    override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture): Boolean {
+        dispatchSurfaceDestroyed(surfaceTextureSurface!!)
+        surfaceTextureSurface = null
+        return true
+    }
+
+    override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
+        // onSurfaceTextureUpdated is called when the content of the SurfaceTexture
+        // has changed, which is not relevant to us since we are the producer here
+    }
+}
+
+@Composable
+private fun rememberEmbeddedGraphicsSurfaceState(): EmbeddedGraphicsSurfaceState {
+    val scope = rememberCoroutineScope()
+    return remember { EmbeddedGraphicsSurfaceState(scope) }
+}
+
+/**
+ * Provides a dedicated drawing [Surface] embedded directly in the UI hierarchy.
+ * Unlike [GraphicsSurface], [EmbeddedGraphicsSurface] positions its surface as a
+ * regular element inside the composable hierarchy. This means that graphics
+ * composition is handled like any other UI widget, using the GPU. This can lead
+ * to increased power and memory bandwidth usage compared to [GraphicsSurface]. It
+ * is therefore recommended to use [GraphicsSurface] when possible.
+ *
+ * [EmbeddedGraphicsSurface] can however be useful when interactions with other widgets
+ * is necessary, for instance if the surface needs to be "sandwiched" between two
+ * other widgets, or if it must participate in visual effects driven by
+ * a `Modifier.graphicsLayer{}`.
+ *
+ * The drawing surface is opaque by default, which can be controlled with the [isOpaque]
+ * parameter.
+ *
+ * To start rendering, the caller must first acquire the [Surface] when it's created.
+ * This is achieved by providing the [onInit] lambda, which allows the caller to
+ * register an appropriate [GraphicsSurfaceScope.onSurface] callback. The [onInit]
+ * lambda can also be used to initialize/cache resources needed once a surface is
+ * available.
+ *
+ * After acquiring a surface, the caller can start rendering into it. Rendering into a
+ * surface can be done from any thread.
+ *
+ * It is recommended to register the [SurfaceScope.onChanged] and [SurfaceScope.onDestroyed]
+ * callbacks to properly handle the lifecycle of the surface and react to dimension
+ * changes. You must ensure that the rendering thread stops interacting with the surface
+ * when the [SurfaceScope.onDestroyed] callback is invoked.
+ *
+ * If a [surfaceSize] is specified (set to non-[IntSize.Zero]), the surface will use
+ * the specified size instead of the layout size of this composable. The surface will
+ * be stretched at render time to fit the layout size. This can be used for instance to
+ * render at a lower resolution for performance reasons.
+ *
+ * @param modifier Modifier to be applied to the [GraphicsSurface]
+ * @param isOpaque Whether the managed surface should be opaque or transparent. If
+ *                 transparent and [isMediaOverlay] is `false`, the surface will
+ *                 be positioned above the parent window.
+ * @param surfaceSize Sets the surface size independently of the layout size of
+ *                    this [GraphicsSurface]. If set to [IntSize.Zero], the surface
+ *                    size will be equal to the [GraphicsSurface] layout size.
+ * @param onInit Lambda invoked on first composition. This lambda can be used to
+ *               declare a [GraphicsSurfaceScope.onSurface] callback that will be
+ *               invoked when a surface is available.
+ *
+ * @sample androidx.compose.foundation.samples.EmbeddedGraphicsSurfaceColors
+ */
+@Composable
+fun EmbeddedGraphicsSurface(
+    modifier: Modifier = Modifier,
+    isOpaque: Boolean = true,
+    surfaceSize: IntSize = IntSize.Zero,
+    onInit: GraphicsSurfaceScope.() -> Unit
+) {
+    val state = rememberEmbeddedGraphicsSurfaceState()
+
+    AndroidView(
+        factory = { context ->
+            TextureView(context).apply {
+                state.surfaceSize = surfaceSize
+                state.onInit()
+                surfaceTextureListener = state
+            }
+        },
+        modifier = modifier,
+        onReset = { },
+        update = { view ->
+            if (surfaceSize != IntSize.Zero) {
+                view.surfaceTexture?.setDefaultBufferSize(surfaceSize.width, surfaceSize.height)
+            }
+            state.surfaceSize = surfaceSize
+            view.isOpaque = isOpaque
+        }
+    )
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.kt
index 736e938..6368cb1 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Magnifier.kt
@@ -20,7 +20,6 @@
 import android.view.View
 import android.widget.Magnifier
 import androidx.annotation.ChecksSdkIntAtLeast
-import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -62,145 +61,13 @@
     SemanticsPropertyKey<() -> Offset>("MagnifierPositionInRoot")
 
 /**
- * Specifies how a [magnifier] should create the underlying [Magnifier] widget. These properties
- * should not be changed while a magnifier is showing, since the magnifier will be dismissed and
- * recreated with the new properties which will cause it to disappear for at least a frame.
- *
- * Not all magnifier features are supported on all platforms. The [isSupported] property will return
- * false for styles that cannot be fully supported on the given platform.
- *
- * @param size See [Magnifier.Builder.setSize]. Only supported on API 29+.
- * @param cornerRadius See [Magnifier.Builder.setCornerRadius]. Only supported on API 29+.
- * @param elevation See [Magnifier.Builder.setElevation]. Only supported on API 29+.
- * @param clippingEnabled See [Magnifier.Builder.setClippingEnabled]. Only supported on API 29+.
- * @param fishEyeEnabled Configures the magnifier to distort the magnification at the edges to
- * look like a fisheye lens. Not currently supported.
- */
-@ExperimentalFoundationApi
-@Stable
-class MagnifierStyle internal constructor(
-    internal val useTextDefault: Boolean,
-    internal val size: DpSize,
-    internal val cornerRadius: Dp,
-    internal val elevation: Dp,
-    internal val clippingEnabled: Boolean,
-    internal val fishEyeEnabled: Boolean
-) {
-    @ExperimentalFoundationApi
-    constructor(
-        size: DpSize = DpSize.Unspecified,
-        cornerRadius: Dp = Dp.Unspecified,
-        elevation: Dp = Dp.Unspecified,
-        clippingEnabled: Boolean = true,
-        fishEyeEnabled: Boolean = false
-    ) : this(
-        useTextDefault = false,
-        size = size,
-        cornerRadius = cornerRadius,
-        elevation = elevation,
-        clippingEnabled = clippingEnabled,
-        fishEyeEnabled = fishEyeEnabled,
-    )
-
-    /**
-     * Returns true if this style is supported by this version of the platform.
-     * When false is returned, it may be either because the [Magnifier] widget is not supported at
-     * all because the platform is too old, or because a particular style flag (e.g.
-     * [fishEyeEnabled]) is not supported on the current platform.
-     * [Default] and [TextDefault] styles are supported on all platforms with SDK version 28 and
-     * higher.
-     */
-    val isSupported: Boolean
-        get() = isStyleSupported(this)
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is MagnifierStyle) return false
-
-        if (useTextDefault != other.useTextDefault) return false
-        if (size != other.size) return false
-        if (cornerRadius != other.cornerRadius) return false
-        if (elevation != other.elevation) return false
-        if (clippingEnabled != other.clippingEnabled) return false
-        if (fishEyeEnabled != other.fishEyeEnabled) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = useTextDefault.hashCode()
-        result = 31 * result + size.hashCode()
-        result = 31 * result + cornerRadius.hashCode()
-        result = 31 * result + elevation.hashCode()
-        result = 31 * result + clippingEnabled.hashCode()
-        result = 31 * result + fishEyeEnabled.hashCode()
-        return result
-    }
-
-    override fun toString(): String {
-        return if (useTextDefault) {
-            "MagnifierStyle.TextDefault"
-        } else {
-            "MagnifierStyle(" +
-                "size=$size, " +
-                "cornerRadius=$cornerRadius, " +
-                "elevation=$elevation, " +
-                "clippingEnabled=$clippingEnabled, " +
-                "fishEyeEnabled=$fishEyeEnabled" +
-                ")"
-        }
-    }
-
-    companion object {
-        /** A [MagnifierStyle] with all default values. */
-        @ExperimentalFoundationApi
-        val Default = MagnifierStyle()
-
-        /**
-         * A [MagnifierStyle] that uses the system defaults for text magnification.
-         *
-         * Different versions of Android may use different magnifier styles for magnifying text, so
-         * using this configuration ensures that the correct style is used to match the system.
-         */
-        @ExperimentalFoundationApi
-        val TextDefault = MagnifierStyle(
-            useTextDefault = true,
-            size = Default.size,
-            cornerRadius = Default.cornerRadius,
-            elevation = Default.elevation,
-            clippingEnabled = Default.clippingEnabled,
-            fishEyeEnabled = Default.fishEyeEnabled,
-        )
-
-        internal fun isStyleSupported(
-            style: MagnifierStyle,
-            sdkVersion: Int = Build.VERSION.SDK_INT
-        ): Boolean {
-            return if (!isPlatformMagnifierSupported(sdkVersion)) {
-                // Older platform versions don't support magnifier at all.
-                false
-            } else if (style.fishEyeEnabled) {
-                // TODO(b/202451044) Add fisheye support once platform APIs are exposed.
-                false
-            } else if (style.useTextDefault || style == Default) {
-                // Default styles are always available on all platforms that support magnifier.
-                true
-            } else {
-                // Custom styles aren't supported on API 28.
-                sdkVersion >= 29
-            }
-        }
-    }
-}
-
-/**
  * Shows a [Magnifier] widget that shows an enlarged version of the content at [sourceCenter]
  * relative to the current layout node.
  *
  * This function returns a no-op modifier on API levels below P (28), since the framework does not
  * support the [Magnifier] widget on those levels. However, even on higher API levels, not all
- * magnifier features are supported on all platforms. To check whether a given [MagnifierStyle] is
- * supported by the current platform, check the [MagnifierStyle.isSupported] property.
+ * magnifier features are supported on all platforms. Please refer to parameter explanations below
+ * to learn more about supported features on different platform versions.
  *
  * This function does not allow configuration of [source bounds][Magnifier.Builder.setSourceBounds]
  * since the magnifier widget does not support constraining to the bounds of composables.
@@ -215,29 +82,69 @@
  * the layout node this modifier is applied to. If [unspecified][DpOffset.Unspecified], the
  * magnifier widget will be placed at a default offset relative to [sourceCenter]. The value of that
  * offset is specified by the system.
- * @param zoom See [Magnifier.setZoom]. Not supported on SDK levels < Q.
- * @param style The [MagnifierStyle] to use to configure the magnifier widget.
  * @param onSizeChanged An optional callback that will be invoked when the magnifier widget is
- * initialized to report on its actual size. This can be useful if one of the default
- * [MagnifierStyle]s is used to find out what size the system decided to use for the widget.
+ * initialized to report on its actual size. This can be useful when [size] parameter is left
+ * unspecified.
+ * @param zoom See [Magnifier.setZoom]. Only supported on API 29+.
+ * @param size See [Magnifier.Builder.setSize]. Only supported on API 29+.
+ * @param cornerRadius See [Magnifier.Builder.setCornerRadius]. Only supported on API 29+.
+ * @param elevation See [Magnifier.Builder.setElevation]. Only supported on API 29+.
+ * @param clippingEnabled See [Magnifier.Builder.setClippingEnabled]. Only supported on API 29+.
  */
-@ExperimentalFoundationApi
 fun Modifier.magnifier(
     sourceCenter: Density.() -> Offset,
     magnifierCenter: Density.() -> Offset = { Offset.Unspecified },
+    onSizeChanged: ((DpSize) -> Unit)? = null,
     zoom: Float = Float.NaN,
-    style: MagnifierStyle = MagnifierStyle.Default,
-    onSizeChanged: ((DpSize) -> Unit)? = null
+    size: DpSize = DpSize.Unspecified,
+    cornerRadius: Dp = Dp.Unspecified,
+    elevation: Dp = Dp.Unspecified,
+    clippingEnabled: Boolean = true
+): Modifier {
+    return magnifier(
+        sourceCenter = sourceCenter,
+        magnifierCenter = magnifierCenter,
+        onSizeChanged = onSizeChanged,
+        zoom = zoom,
+        useTextDefault = false,
+        size = size,
+        cornerRadius = cornerRadius,
+        elevation = elevation,
+        clippingEnabled = clippingEnabled
+    )
+}
+
+/**
+ * For testing and internal Text usage purposes.
+ *
+ * TextField and SelectionManager uses this internal API to pass `useTextDefault` as true.
+ */
+internal fun Modifier.magnifier(
+    sourceCenter: Density.() -> Offset,
+    magnifierCenter: Density.() -> Offset = { Offset.Unspecified },
+    onSizeChanged: ((DpSize) -> Unit)? = null,
+    zoom: Float = Float.NaN,
+    useTextDefault: Boolean = false,
+    size: DpSize = DpSize.Unspecified,
+    cornerRadius: Dp = Dp.Unspecified,
+    elevation: Dp = Dp.Unspecified,
+    clippingEnabled: Boolean = true,
+    platformMagnifierFactory: PlatformMagnifierFactory? = null
 ): Modifier {
     return if (isPlatformMagnifierSupported()) {
         then(
             MagnifierElement(
                 sourceCenter = sourceCenter,
                 magnifierCenter = magnifierCenter,
-                zoom = zoom,
-                style = style,
                 onSizeChanged = onSizeChanged,
-                platformMagnifierFactory = PlatformMagnifierFactory.getForCurrentPlatform()
+                useTextDefault = useTextDefault,
+                zoom = zoom,
+                size = size,
+                cornerRadius = cornerRadius,
+                elevation = elevation,
+                clippingEnabled = clippingEnabled,
+                platformMagnifierFactory = platformMagnifierFactory
+                    ?: PlatformMagnifierFactory.getForCurrentPlatform() // this doesn't do an alloc
             )
         )
     } else {
@@ -251,50 +158,25 @@
                 properties["sourceCenter"] = sourceCenter
                 properties["magnifierCenter"] = magnifierCenter
                 properties["zoom"] = zoom
-                properties["style"] = style
+                properties["size"] = size
+                properties["cornerRadius"] = cornerRadius
+                properties["elevation"] = elevation
+                properties["clippingEnabled"] = clippingEnabled
             }
         ) { this }
     }
 }
 
-/**
- * For testing purposes.
- */
-@OptIn(ExperimentalFoundationApi::class)
-internal fun Modifier.magnifier(
-    sourceCenter: Density.() -> Offset,
-    magnifierCenter: Density.() -> Offset = { Offset.Unspecified },
-    zoom: Float = Float.NaN,
-    style: MagnifierStyle = MagnifierStyle.Default,
-    onSizeChanged: ((DpSize) -> Unit)? = null,
-    platformMagnifierFactory: PlatformMagnifierFactory
-): Modifier {
-    return if (isPlatformMagnifierSupported()) {
-        then(
-            MagnifierElement(
-                sourceCenter = sourceCenter,
-                magnifierCenter = magnifierCenter,
-                zoom = zoom,
-                style = style,
-                onSizeChanged = onSizeChanged,
-                platformMagnifierFactory = platformMagnifierFactory
-            )
-        )
-    } else {
-        // Magnifier is only supported in >=28. So avoid doing all the work to manage the magnifier
-        // state if it's not needed.
-        // TODO(b/202739980) Investigate supporting Magnifier on earlier versions.
-        Modifier
-    }
-}
-
-@OptIn(ExperimentalFoundationApi::class)
 internal class MagnifierElement(
     private val sourceCenter: Density.() -> Offset,
     private val magnifierCenter: Density.() -> Offset = { Offset.Unspecified },
-    private val zoom: Float = Float.NaN,
-    private val style: MagnifierStyle = MagnifierStyle.Default,
     private val onSizeChanged: ((DpSize) -> Unit)? = null,
+    private val zoom: Float = Float.NaN,
+    private val useTextDefault: Boolean = false,
+    private val size: DpSize = DpSize.Unspecified,
+    private val cornerRadius: Dp = Dp.Unspecified,
+    private val elevation: Dp = Dp.Unspecified,
+    private val clippingEnabled: Boolean = true,
     private val platformMagnifierFactory: PlatformMagnifierFactory
 ) : ModifierNodeElement<MagnifierNode>() {
 
@@ -303,7 +185,11 @@
             sourceCenter = sourceCenter,
             magnifierCenter = magnifierCenter,
             zoom = zoom,
-            style = style,
+            useTextDefault = useTextDefault,
+            size = size,
+            cornerRadius = cornerRadius,
+            elevation = elevation,
+            clippingEnabled = clippingEnabled,
             onSizeChanged = onSizeChanged,
             platformMagnifierFactory = platformMagnifierFactory
         )
@@ -314,7 +200,11 @@
             sourceCenter = sourceCenter,
             magnifierCenter = magnifierCenter,
             zoom = zoom,
-            style = style,
+            useTextDefault = useTextDefault,
+            size = size,
+            cornerRadius = cornerRadius,
+            elevation = elevation,
+            clippingEnabled = clippingEnabled,
             onSizeChanged = onSizeChanged,
             platformMagnifierFactory = platformMagnifierFactory
         )
@@ -327,7 +217,11 @@
         if (sourceCenter != other.sourceCenter) return false
         if (magnifierCenter != other.magnifierCenter) return false
         if (zoom != other.zoom) return false
-        if (style != other.style) return false
+        if (useTextDefault != other.useTextDefault) return false
+        if (size != other.size) return false
+        if (cornerRadius != other.cornerRadius) return false
+        if (elevation != other.elevation) return false
+        if (clippingEnabled != other.clippingEnabled) return false
         if (onSizeChanged != other.onSizeChanged) return false
         if (platformMagnifierFactory != other.platformMagnifierFactory) return false
 
@@ -338,7 +232,11 @@
         var result = sourceCenter.hashCode()
         result = 31 * result + magnifierCenter.hashCode()
         result = 31 * result + zoom.hashCode()
-        result = 31 * result + style.hashCode()
+        result = 31 * result + useTextDefault.hashCode()
+        result = 31 * result + size.hashCode()
+        result = 31 * result + cornerRadius.hashCode()
+        result = 31 * result + elevation.hashCode()
+        result = 31 * result + clippingEnabled.hashCode()
         result = 31 * result + (onSizeChanged?.hashCode() ?: 0)
         result = 31 * result + platformMagnifierFactory.hashCode()
         return result
@@ -349,17 +247,23 @@
         properties["sourceCenter"] = sourceCenter
         properties["magnifierCenter"] = magnifierCenter
         properties["zoom"] = zoom
-        properties["style"] = style
+        properties["size"] = size
+        properties["cornerRadius"] = cornerRadius
+        properties["elevation"] = elevation
+        properties["clippingEnabled"] = clippingEnabled
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 internal class MagnifierNode(
     var sourceCenter: Density.() -> Offset,
     var magnifierCenter: Density.() -> Offset = { Offset.Unspecified },
-    var zoom: Float = Float.NaN,
-    var style: MagnifierStyle = MagnifierStyle.Default,
     var onSizeChanged: ((DpSize) -> Unit)? = null,
+    var zoom: Float = Float.NaN,
+    var useTextDefault: Boolean = false,
+    var size: DpSize = DpSize.Unspecified,
+    var cornerRadius: Dp = Dp.Unspecified,
+    var elevation: Dp = Dp.Unspecified,
+    var clippingEnabled: Boolean = true,
     var platformMagnifierFactory: PlatformMagnifierFactory =
         PlatformMagnifierFactory.getForCurrentPlatform()
 ) : Modifier.Node(),
@@ -406,18 +310,29 @@
         sourceCenter: Density.() -> Offset,
         magnifierCenter: Density.() -> Offset,
         zoom: Float,
-        style: MagnifierStyle,
+        useTextDefault: Boolean,
+        size: DpSize,
+        cornerRadius: Dp,
+        elevation: Dp,
+        clippingEnabled: Boolean,
         onSizeChanged: ((DpSize) -> Unit)?,
         platformMagnifierFactory: PlatformMagnifierFactory
     ) {
         val previousZoom = this.zoom
-        val previousStyle = this.style
+        val previousSize = this.size
+        val previousCornerRadius = this.cornerRadius
+        val previousElevation = this.elevation
+        val previousClippingEnabled = this.clippingEnabled
         val previousPlatformMagnifierFactory = this.platformMagnifierFactory
 
         this.sourceCenter = sourceCenter
         this.magnifierCenter = magnifierCenter
         this.zoom = zoom
-        this.style = style
+        this.useTextDefault = useTextDefault
+        this.size = size
+        this.cornerRadius = cornerRadius
+        this.elevation = elevation
+        this.clippingEnabled = clippingEnabled
         this.onSizeChanged = onSizeChanged
         this.platformMagnifierFactory = platformMagnifierFactory
 
@@ -425,10 +340,15 @@
         // if the zoom changes between recompositions we don't need to recreate the magnifier. On
         // older platforms, the zoom can only be set initially, so we use the zoom itself as a key
         // so the magnifier gets recreated if it changes.
-        if (magnifier == null ||
+        if (
+            magnifier == null ||
             (zoom != previousZoom && !platformMagnifierFactory.canUpdateZoom) ||
-            style != previousStyle ||
-            platformMagnifierFactory != previousPlatformMagnifierFactory) {
+            size != previousSize ||
+            cornerRadius != previousCornerRadius ||
+            elevation != previousElevation ||
+            clippingEnabled != previousClippingEnabled ||
+            platformMagnifierFactory != previousPlatformMagnifierFactory
+        ) {
             recreateMagnifier()
         }
         updateMagnifier()
@@ -462,7 +382,16 @@
         magnifier?.dismiss()
         val view = view ?: return
         val density = density ?: return
-        magnifier = platformMagnifierFactory.create(style, view, density, zoom)
+        magnifier = platformMagnifierFactory.create(
+            view = view,
+            useTextDefault = useTextDefault,
+            size = size,
+            cornerRadius = cornerRadius,
+            elevation = elevation,
+            clippingEnabled = clippingEnabled,
+            density = density,
+            initialZoom = zoom
+        )
         updateSizeIfNecessary()
     }
 
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/PlatformMagnifier.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/PlatformMagnifier.kt
index 91f6658..86ce694 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/PlatformMagnifier.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/PlatformMagnifier.kt
@@ -24,6 +24,8 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.isSpecified
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.IntSize
 import kotlin.math.roundToInt
 
@@ -37,10 +39,13 @@
      */
     val canUpdateZoom: Boolean
 
-    @OptIn(ExperimentalFoundationApi::class)
     fun create(
-        style: MagnifierStyle,
         view: View,
+        useTextDefault: Boolean,
+        size: DpSize,
+        cornerRadius: Dp,
+        elevation: Dp,
+        clippingEnabled: Boolean,
         density: Density,
         initialZoom: Float
     ): PlatformMagnifier
@@ -90,10 +95,13 @@
     override val canUpdateZoom: Boolean = false
 
     @Suppress("DEPRECATION")
-    @OptIn(ExperimentalFoundationApi::class)
     override fun create(
-        style: MagnifierStyle,
         view: View,
+        useTextDefault: Boolean,
+        size: DpSize,
+        cornerRadius: Dp,
+        elevation: Dp,
+        clippingEnabled: Boolean,
         density: Density,
         initialZoom: Float
     ): PlatformMagnifierImpl = PlatformMagnifierImpl(Magnifier(view))
@@ -126,35 +134,45 @@
 internal object PlatformMagnifierFactoryApi29Impl : PlatformMagnifierFactory {
     override val canUpdateZoom: Boolean = true
 
-    @OptIn(ExperimentalFoundationApi::class)
     override fun create(
-        style: MagnifierStyle,
         view: View,
+        useTextDefault: Boolean,
+        size: DpSize,
+        cornerRadius: Dp,
+        elevation: Dp,
+        clippingEnabled: Boolean,
         density: Density,
         initialZoom: Float
     ): PlatformMagnifierImpl {
         with(density) {
             // TODO write test for this branch
-            if (style == MagnifierStyle.TextDefault) {
+            if (useTextDefault) {
                 // This deprecated constructor is the only public API to create a Magnifier that
                 // uses the system text magnifier defaults.
                 @Suppress("DEPRECATION")
                 return PlatformMagnifierImpl(Magnifier(view))
             }
 
-            val size = style.size.toSize()
-            val cornerRadius = style.cornerRadius.toPx()
-            val elevation = style.elevation.toPx()
+            val pixelSize = size.toSize()
+            val pixelCornerRadius = cornerRadius.toPx()
+            val pixelElevation = elevation.toPx()
 
             // When Builder properties are not specified, the widget uses different defaults than it
             // does for the non-builder constructor above.
             val magnifier = Magnifier.Builder(view).run {
-                if (size.isSpecified) setSize(size.width.roundToInt(), size.height.roundToInt())
-                if (!cornerRadius.isNaN()) setCornerRadius(cornerRadius)
-                if (!elevation.isNaN()) setElevation(elevation)
-                if (!initialZoom.isNaN()) setInitialZoom(initialZoom)
-                setClippingEnabled(style.clippingEnabled)
-                // TODO(b/202451044) Support setting fisheye style.
+                if (pixelSize.isSpecified) {
+                    setSize(pixelSize.width.roundToInt(), pixelSize.height.roundToInt())
+                }
+                if (!pixelCornerRadius.isNaN()) {
+                    setCornerRadius(pixelCornerRadius)
+                }
+                if (!pixelElevation.isNaN()) {
+                    setElevation(pixelElevation)
+                }
+                if (!initialZoom.isNaN()) {
+                    setInitialZoom(initialZoom)
+                }
+                setClippingEnabled(clippingEnabled)
                 build()
             }
 
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.android.kt
index cfac198..23e6c8e 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.android.kt
@@ -16,8 +16,8 @@
 
 package androidx.compose.foundation.text.selection
 
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.MagnifierStyle
+import androidx.compose.foundation.PlatformMagnifierFactory
+import androidx.compose.foundation.isPlatformMagnifierSupported
 import androidx.compose.foundation.magnifier
 import androidx.compose.foundation.text.KeyCommand
 import androidx.compose.foundation.text.platformDefaultKeyMapping
@@ -36,10 +36,9 @@
 
 // We use composed{} to read a local, but don't provide inspector info because the underlying
 // magnifier modifier provides more meaningful inspector info.
-@OptIn(ExperimentalFoundationApi::class)
 internal actual fun Modifier.selectionMagnifier(manager: SelectionManager): Modifier {
     // Avoid tracking animation state on older Android versions that don't support magnifiers.
-    if (!MagnifierStyle.TextDefault.isSupported) {
+    if (!isPlatformMagnifierSupported()) {
         return this
     }
 
@@ -59,7 +58,8 @@
                                 IntSize(size.width.roundToPx(), size.height.roundToPx())
                             }
                         },
-                        style = MagnifierStyle.TextDefault
+                        useTextDefault = true,
+                        platformMagnifierFactory = PlatformMagnifierFactory.getForCurrentPlatform()
                     )
             }
         )
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.android.kt
index e750b21..c888776 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.android.kt
@@ -17,7 +17,8 @@
 package androidx.compose.foundation.text.selection
 
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.MagnifierStyle
+import androidx.compose.foundation.PlatformMagnifierFactory
+import androidx.compose.foundation.isPlatformMagnifierSupported
 import androidx.compose.foundation.magnifier
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -37,7 +38,7 @@
 @OptIn(ExperimentalFoundationApi::class)
 internal actual fun Modifier.textFieldMagnifier(manager: TextFieldSelectionManager): Modifier {
     // Avoid tracking animation state on older Android versions that don't support magnifiers.
-    if (!MagnifierStyle.TextDefault.isSupported) {
+    if (!isPlatformMagnifierSupported()) {
         return this
     }
 
@@ -56,8 +57,8 @@
                             IntSize(size.width.roundToPx(), size.height.roundToPx())
                         }
                     },
-                    // TODO(b/202451044) Support fisheye magnifier for eloquent.
-                    style = MagnifierStyle.TextDefault
+                    useTextDefault = true,
+                    platformMagnifierFactory = PlatformMagnifierFactory.getForCurrentPlatform()
                 )
             }
         )
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputSession.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputSession.android.kt
index 671b253..181cedc 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputSession.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/AndroidTextInputSession.android.kt
@@ -26,9 +26,7 @@
 import android.view.inputmethod.InputConnection
 import androidx.annotation.VisibleForTesting
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.InputTransformation
 import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.foundation.text2.input.TextFieldState
 import androidx.compose.ui.platform.PlatformTextInputSession
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.ImeOptions
@@ -39,7 +37,6 @@
 import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.suspendCancellableCoroutine
 import org.jetbrains.annotations.TestOnly
 
 /** Enable to print logs during debugging, see [logDebug]. */
@@ -70,10 +67,10 @@
     inputConnectionInterceptor = interceptor
 }
 
+@OptIn(ExperimentalFoundationApi::class)
 internal actual suspend fun PlatformTextInputSession.platformSpecificTextInputSession(
-    state: TextFieldState,
+    state: TransformedTextFieldState,
     imeOptions: ImeOptions,
-    filter: InputTransformation?,
     onImeAction: ((ImeAction) -> Unit)?
 ): Nothing {
     val composeImm = ComposeInputMethodManager(view)
@@ -107,8 +104,7 @@
                     get() = state.text
 
                 override fun requestEdit(block: EditingBuffer.() -> Unit) {
-                    state.editAsUser(
-                        inputTransformation = filter,
+                    state.editUntransformedTextAsUser(
                         notifyImeOfChanges = false,
                         block = block
                     )
@@ -231,21 +227,6 @@
     this.imeOptions = this.imeOptions or EditorInfo.IME_FLAG_NO_FULLSCREEN
 }
 
-/**
- * Adds [notifyImeListener] to this [TextFieldState] and then suspends until cancelled, removing the
- * listener before continuing.
- */
-private suspend inline fun TextFieldState.collectImeNotifications(
-    notifyImeListener: TextFieldState.NotifyImeListener
-): Nothing {
-    suspendCancellableCoroutine<Nothing> { continuation ->
-        addNotifyImeListener(notifyImeListener)
-        continuation.invokeOnCancellation {
-            removeNotifyImeListener(notifyImeListener)
-        }
-    }
-}
-
 private fun hasFlag(bits: Int, flag: Int): Boolean = (bits and flag) == flag
 
 private fun logDebug(tag: String = TAG, content: () -> String) {
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.android.kt
index 8a1a51d..2e48e57 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.android.kt
@@ -22,8 +22,6 @@
 import android.view.KeyEvent.KEYCODE_DPAD_LEFT
 import android.view.KeyEvent.KEYCODE_DPAD_RIGHT
 import android.view.KeyEvent.KEYCODE_DPAD_UP
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.TextFieldState
 import androidx.compose.foundation.text2.input.internal.selection.TextFieldSelectionState
 import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.FocusManager
@@ -37,12 +35,11 @@
 internal actual fun createTextFieldKeyEventHandler(): TextFieldKeyEventHandler =
     AndroidTextFieldKeyEventHandler()
 
-@OptIn(ExperimentalFoundationApi::class)
 internal class AndroidTextFieldKeyEventHandler : TextFieldKeyEventHandler() {
 
     override fun onPreKeyEvent(
         event: KeyEvent,
-        textFieldState: TextFieldState,
+        textFieldState: TransformedTextFieldState,
         textFieldSelectionState: TextFieldSelectionState,
         focusManager: FocusManager,
         keyboardController: SoftwareKeyboardController
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/AndroidTextFieldMagnifier.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/AndroidTextFieldMagnifier.kt
index 9edd02f..27250b3 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/AndroidTextFieldMagnifier.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/AndroidTextFieldMagnifier.kt
@@ -20,13 +20,12 @@
 import androidx.compose.animation.core.Animatable
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.MagnifierNode
-import androidx.compose.foundation.MagnifierStyle
 import androidx.compose.foundation.isPlatformMagnifierSupported
 import androidx.compose.foundation.text.selection.MagnifierSpringSpec
 import androidx.compose.foundation.text.selection.OffsetDisplacementThreshold
 import androidx.compose.foundation.text.selection.UnspecifiedSafeOffsetVectorConverter
-import androidx.compose.foundation.text2.input.TextFieldState
 import androidx.compose.foundation.text2.input.internal.TextLayoutState
+import androidx.compose.foundation.text2.input.internal.TransformedTextFieldState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -43,8 +42,8 @@
 import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalFoundationApi::class)
-internal class TextFieldMagnifierNodeImpl28 constructor(
-    private var textFieldState: TextFieldState,
+internal class TextFieldMagnifierNodeImpl28(
+    private var textFieldState: TransformedTextFieldState,
     private var textFieldSelectionState: TextFieldSelectionState,
     private var textLayoutState: TextLayoutState,
     private var isFocused: Boolean
@@ -73,8 +72,7 @@
                     IntSize(size.width.roundToPx(), size.height.roundToPx())
                 }
             },
-            // TODO(b/202451044) Support fisheye magnifier for eloquent.
-            style = MagnifierStyle.TextDefault
+            useTextDefault = true
         )
     )
 
@@ -85,7 +83,7 @@
     }
 
     override fun update(
-        textFieldState: TextFieldState,
+        textFieldState: TransformedTextFieldState,
         textFieldSelectionState: TextFieldSelectionState,
         textLayoutState: TextLayoutState,
         isFocused: Boolean
@@ -114,7 +112,7 @@
         animationJob = null
         // never start an expensive animation job if we do not have focus or
         // magnifier is not supported.
-        if (!isFocused || !MagnifierStyle.TextDefault.isSupported) return
+        if (!isFocused || !isPlatformMagnifierSupported()) return
         animationJob = coroutineScope.launch {
             val animationScope = this
             snapshotFlow {
@@ -170,9 +168,8 @@
  * whether magnifier is supported.
  */
 @SuppressLint("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
-@OptIn(ExperimentalFoundationApi::class)
 internal actual fun textFieldMagnifierNode(
-    textFieldState: TextFieldState,
+    textFieldState: TransformedTextFieldState,
     textFieldSelectionState: TextFieldSelectionState,
     textLayoutState: TextLayoutState,
     isFocused: Boolean
@@ -187,7 +184,7 @@
     } else {
         object : TextFieldMagnifierNode() {
             override fun update(
-                textFieldState: TextFieldState,
+                textFieldState: TransformedTextFieldState,
                 textFieldSelectionState: TextFieldSelectionState,
                 textLayoutState: TextLayoutState,
                 isFocused: Boolean
diff --git a/compose/foundation/foundation/src/androidMain/res/values-af/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-af/strings.xml
new file mode 100644
index 0000000..3e16881
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-af/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"nutswenk"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"wys nutswenk"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-am/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-am/strings.xml
new file mode 100644
index 0000000..f5faadd
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-am/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"መሣሪያ ጥቆማ"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"መሣሪያ ጥቆማን አሳይ"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-ar/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-ar/strings.xml
new file mode 100644
index 0000000..b335327
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-ar/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"تلميح"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"إظهار التلميح"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-as/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-as/strings.xml
new file mode 100644
index 0000000..1465db9
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-as/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"টুলটিপ"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"টুলটিপ দেখুৱাওক"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-az/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-az/strings.xml
new file mode 100644
index 0000000..3a1a1b9
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-az/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"alət izahı"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"alət izahını göstərin"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-b+sr+Latn/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..0486489
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"objašnjenje"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"prikaži objašnjenje"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-be/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-be/strings.xml
new file mode 100644
index 0000000..b9c7e47
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-be/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"падказка"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"паказаць усплывальную падказку"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-bg/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-bg/strings.xml
new file mode 100644
index 0000000..f28bfc1
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-bg/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"подсказка"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"показване на подсказка"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-bn/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-bn/strings.xml
new file mode 100644
index 0000000..3c52482
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-bn/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"টুলটিপ"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"টুলটিপ দেখুন"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-bs/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-bs/strings.xml
new file mode 100644
index 0000000..ae6e561
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-bs/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"skočni opis"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"prikaži skočni opis"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-ca/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-ca/strings.xml
new file mode 100644
index 0000000..24b583d
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-ca/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"descripció emergent"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"mostra la descripció emergent"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-cs/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-cs/strings.xml
new file mode 100644
index 0000000..521a2d7
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-cs/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"popisek"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"zobrazit popisek"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-da/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-da/strings.xml
new file mode 100644
index 0000000..0326ad43
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-da/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"værktøjstip"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"se værktøjstip"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-de/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-de/strings.xml
new file mode 100644
index 0000000..ef00062
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-de/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"Kurzinfo"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"Kurzinfo anzeigen"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-el/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-el/strings.xml
new file mode 100644
index 0000000..1adc933
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-el/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"επεξήγηση εργαλείου"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"εμφάνιση επεξήγησης εργαλείου"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-en-rAU/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..2cbf75c
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-en-rAU/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"tooltip"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"show tooltip"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-en-rCA/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..2cbf75c
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-en-rCA/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"tooltip"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"show tooltip"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-en-rGB/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..2cbf75c
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-en-rGB/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"tooltip"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"show tooltip"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-en-rIN/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..2cbf75c
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-en-rIN/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"tooltip"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"show tooltip"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-en-rXC/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..24c5541a
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-en-rXC/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‎‏‎‎‎‎‏‏‎‏‏‏‎‎‎‎‏‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‏‏‎‏‎‏‎‏‏‎tooltip‎‏‎‎‏‎"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‎‎‎‎‎‏‎‎‎‎‏‎‎‏‏‏‎‎‎‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‎‏‎‎‎‎show tooltip‎‏‎‎‏‎"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-es-rUS/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..c887b53
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-es-rUS/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"cuadro de información"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"mostrar cuadro de información"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-es/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-es/strings.xml
new file mode 100644
index 0000000..6115bee
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-es/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"descripción emergente"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"mostrar descripción emergente"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-et/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-et/strings.xml
new file mode 100644
index 0000000..eccb264
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-et/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"kohtspikker"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"kuva kohtspikker"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-eu/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-eu/strings.xml
new file mode 100644
index 0000000..0950e7f
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-eu/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"aholkua"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"erakutsi aholkua"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-fa/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-fa/strings.xml
new file mode 100644
index 0000000..413efba
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-fa/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"نکته‌ابزار"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"نمایش نکته‌ابزار"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-fi/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-fi/strings.xml
new file mode 100644
index 0000000..6326d6d
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-fi/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"vihjeteksti"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"näytä vihjeteksti"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-fr-rCA/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..592d87f
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-fr-rCA/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"infobulle"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"afficher une infobulle"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-fr/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-fr/strings.xml
new file mode 100644
index 0000000..81bfcff
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-fr/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"Info-bulle"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"Afficher l\'info-bulle"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-gl/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-gl/strings.xml
new file mode 100644
index 0000000..35fa851
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-gl/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"cadro de información"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"mostrar cadro de información"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-gu/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-gu/strings.xml
new file mode 100644
index 0000000..f52583d
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-gu/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"ટૂલટિપ"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"ટૂલટિપ બતાવો"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-hi/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-hi/strings.xml
new file mode 100644
index 0000000..56436fe
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-hi/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"टूलटिप"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"टूलटिप दिखाएं"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-hr/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-hr/strings.xml
new file mode 100644
index 0000000..b670c78
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-hr/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"opis"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"prikaži opis"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-hu/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-hu/strings.xml
new file mode 100644
index 0000000..9fb835c
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-hu/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"elemleírás"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"elemleírás megjelenítése"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-hy/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-hy/strings.xml
new file mode 100644
index 0000000..b656c32
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-hy/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"հուշակ"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"ցուցադրել հուշակ"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-in/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-in/strings.xml
new file mode 100644
index 0000000..41cbfde
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-in/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"tooltip"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"tampilkan tooltip"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-is/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-is/strings.xml
new file mode 100644
index 0000000..741c33a
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-is/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"ábending"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"sýna ábendingu"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-it/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-it/strings.xml
new file mode 100644
index 0000000..77b02a1
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-it/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"Descrizione comando"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"Mostra descrizione comando"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-iw/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-iw/strings.xml
new file mode 100644
index 0000000..11dddc3
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-iw/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"הסבר קצר"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"הצגת ההסבר הקצר"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-ja/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-ja/strings.xml
new file mode 100644
index 0000000..c9e4b933f
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-ja/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"ツールチップ"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"ツールチップを表示"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-ka/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-ka/strings.xml
new file mode 100644
index 0000000..e75c49a
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-ka/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"მინიშნება"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"მინიშნების ჩვენება"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-kk/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-kk/strings.xml
new file mode 100644
index 0000000..1692d94
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-kk/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"қалқыма көмек"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"қалқыма көмекті көрсету"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-km/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-km/strings.xml
new file mode 100644
index 0000000..52ea3bf
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-km/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"កំណត់​ពន្យល់"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"បង្ហាញ​កំណត់​ពន្យល់"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-kn/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-kn/strings.xml
new file mode 100644
index 0000000..b269a2b
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-kn/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"ಟೂಲ್‌ಟಿಪ್"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"ಟೂಲ್‌ಟಿಪ್ ಅನ್ನು ತೋರಿಸಿ"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-ko/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-ko/strings.xml
new file mode 100644
index 0000000..1459c94
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-ko/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"도움말"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"도움말 표시"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-ky/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-ky/strings.xml
new file mode 100644
index 0000000..4fe83e49
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-ky/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"калкып чыгуучу кеңеш"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"калкып чыгуучу кеңешти көрсөтүү"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-lo/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-lo/strings.xml
new file mode 100644
index 0000000..5f5a0fb
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-lo/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"ຄຳແນະນຳ"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"ສະແດງຄຳແນະນຳ"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-lt/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-lt/strings.xml
new file mode 100644
index 0000000..a9ea489
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-lt/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"patarimas"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"rodyti patarimą"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-lv/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-lv/strings.xml
new file mode 100644
index 0000000..260837f
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-lv/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"rīka padoms"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"rādīt rīka padomu"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-mk/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-mk/strings.xml
new file mode 100644
index 0000000..5adbad2
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-mk/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"совет за алатка"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"прикажи совет за алатка"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-ml/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-ml/strings.xml
new file mode 100644
index 0000000..cbc76bb
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-ml/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"ടൂൾടിപ്പ്"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"ടൂൾടിപ്പ് കാണിക്കുക"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-mn/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-mn/strings.xml
new file mode 100644
index 0000000..0a2d354
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-mn/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"зөвлөмж"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"зөвлөмж харуулах"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-mr/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-mr/strings.xml
new file mode 100644
index 0000000..9770093
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-mr/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"टूलटिप"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"टूलटिप दाखवा"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-ms/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-ms/strings.xml
new file mode 100644
index 0000000..d1caacc
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-ms/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"tip alat"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"tunjukkan tip alat"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-my/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-my/strings.xml
new file mode 100644
index 0000000..ac6d496
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-my/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"အကြံပြုချက်ပြ ပေါ့အပ် ဝင်းဒိုး"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"အကြံပြုချက်ပြ ပေါ့အပ်ဝင်းဒိုး ပြရန်"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-nb/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-nb/strings.xml
new file mode 100644
index 0000000..122538f
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-nb/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"verktøytips"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"vis verktøytips"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-ne/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-ne/strings.xml
new file mode 100644
index 0000000..38911d6
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-ne/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"टुलटिप"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"टुलटिप देखाइयोस्"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-nl/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-nl/strings.xml
new file mode 100644
index 0000000..e465e96
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-nl/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"tooltip"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"tooltip tonen"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-or/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-or/strings.xml
new file mode 100644
index 0000000..d19d550
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-or/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"ଟୁଲଟିପ"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"ଟୁଲଟିପ ଦେଖାନ୍ତୁ"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-pa/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-pa/strings.xml
new file mode 100644
index 0000000..e20db2a
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-pa/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"ਟੂਲ-ਟਿੱਪ"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"ਟੂਲ-ਟਿੱਪ ਦਿਖਾਓ"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-pl/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-pl/strings.xml
new file mode 100644
index 0000000..501fbbf
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-pl/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"etykietka"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"pokaż etykietkę"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-pt-rBR/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..7495f03
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-pt-rBR/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"dica"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"mostrar dica"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-pt-rPT/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..3d32ef4
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-pt-rPT/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"sugestão"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"mostrar sugestão"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-pt/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-pt/strings.xml
new file mode 100644
index 0000000..7495f03
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-pt/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"dica"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"mostrar dica"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-ro/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-ro/strings.xml
new file mode 100644
index 0000000..9667645
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-ro/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"balon explicativ"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"afișează balonul explicativ"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-ru/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-ru/strings.xml
new file mode 100644
index 0000000..fbe1ad9
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-ru/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"Подсказка"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"Показать подсказку"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-si/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-si/strings.xml
new file mode 100644
index 0000000..631ab52
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-si/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"මෙවලම් ඉඟිය"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"මෙවලම් ඉඟිය පෙන්වන්න"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-sk/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-sk/strings.xml
new file mode 100644
index 0000000..9608516
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-sk/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"popis"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"zobraziť popis"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-sl/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-sl/strings.xml
new file mode 100644
index 0000000..bef1544
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-sl/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"opis orodja"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"pokaži opis orodja"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-sq/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-sq/strings.xml
new file mode 100644
index 0000000..9399161
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-sq/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"këshillë për veglën"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"shfaq këshillën për veglën"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-sr/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-sr/strings.xml
new file mode 100644
index 0000000..75af0ed
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-sr/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"објашњење"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"прикажи објашњење"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-sv/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-sv/strings.xml
new file mode 100644
index 0000000..c19f7920
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-sv/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"beskrivning"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"visa beskrivning"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-sw/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-sw/strings.xml
new file mode 100644
index 0000000..d490050
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-sw/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"kidirisha cha vidokezo"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"onyesha kidirisha cha vidokezo"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-ta/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-ta/strings.xml
new file mode 100644
index 0000000..e2f25cc
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-ta/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"உதவிக்குறிப்பு"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"உதவிக்குறிப்பைக் காட்டும்"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-te/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-te/strings.xml
new file mode 100644
index 0000000..7966cb2
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-te/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"టూల్‌టిప్"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"టూల్‌టిప్‌ను చూపించండి"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-th/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-th/strings.xml
new file mode 100644
index 0000000..e3394d1
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-th/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"เคล็ดลับเครื่องมือ"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"แสดงเคล็ดลับเครื่องมือ"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-tl/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-tl/strings.xml
new file mode 100644
index 0000000..0bc2d13
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-tl/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"tooltip"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"ipakita ang tooltip"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-tr/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-tr/strings.xml
new file mode 100644
index 0000000..94a36c6
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-tr/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"ipucu"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"ipucunu göster"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-uk/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-uk/strings.xml
new file mode 100644
index 0000000..57c494a
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-uk/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"спливаюча підказка"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"показати спливаючу підказку"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-ur/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-ur/strings.xml
new file mode 100644
index 0000000..bc52b15
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-ur/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"ٹول ٹپ"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"ٹول ٹپ دکھائیں"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-uz/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-uz/strings.xml
new file mode 100644
index 0000000..f3f79ad
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-uz/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"qalqib chiquvchi maslahat oynasi"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"maslahat oynasini koʻrsatish"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-vi/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-vi/strings.xml
new file mode 100644
index 0000000..ac56a55
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-vi/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"chú thích"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"hiện chú thích"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-zh-rCN/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..a61906c4
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-zh-rCN/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"提示"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"显示提示"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-zh-rHK/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..1d49506
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-zh-rHK/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"提示"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"顯示提示"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-zh-rTW/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..b54a809
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-zh-rTW/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"工具提示"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"顯示工具提示"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/androidMain/res/values-zu/strings.xml b/compose/foundation/foundation/src/androidMain/res/values-zu/strings.xml
new file mode 100644
index 0000000..abc4159
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/res/values-zu/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tooltip_description" msgid="3765533235322692011">"ithulithiphu"</string>
+    <string name="tooltip_label" msgid="3124740595719787496">"bonisa ithulithiphu"</string>
+</resources>
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Background.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Background.kt
index 2a770bc..622afb5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Background.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Background.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation
 
 import androidx.annotation.FloatRange
+import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Brush
@@ -40,6 +41,7 @@
  * @param color color to paint background with
  * @param shape desired shape of the background
  */
+@Stable
 fun Modifier.background(
     color: Color,
     shape: Shape = RectangleShape
@@ -70,6 +72,7 @@
  * @param alpha Opacity to be applied to the [brush], with `0` being completely transparent and
  * `1` being completely opaque. The value must be between `0` and `1`.
  */
+@Stable
 fun Modifier.background(
     brush: Brush,
     shape: Shape = RectangleShape,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicMarquee.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicMarquee.kt
index 4b77a1d..d173f4a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicMarquee.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicMarquee.kt
@@ -135,6 +135,7 @@
  * Note: this modifier and corresponding APIs are experimental pending some refinements in the API
  * surface, mostly related to customisation params.
  */
+@Stable
 @ExperimentalFoundationApi
 fun Modifier.basicMarquee(
     iterations: Int = DefaultMarqueeIterations,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicTooltip.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicTooltip.kt
index 26deed4..dd0593b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicTooltip.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicTooltip.kt
@@ -104,6 +104,7 @@
  * @param mutatorMutex [MutatorMutex] used to ensure that for all of the tooltips associated
  * with the mutator mutex, only one will be shown on the screen at any time.
  */
+@Stable
 fun BasicTooltipState(
     initialIsVisible: Boolean = false,
     isPersistent: Boolean = true,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
index 0bae435..330d630 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation
 
+import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.CacheDrawModifierNode
 import androidx.compose.ui.draw.CacheDrawScope
@@ -63,6 +64,7 @@
  * @param border [BorderStroke] class that specifies border appearance, such as size and color
  * @param shape shape of the border
  */
+@Stable
 fun Modifier.border(border: BorderStroke, shape: Shape = RectangleShape) =
     border(width = border.width, brush = border.brush, shape = shape)
 
@@ -76,6 +78,7 @@
  * @param color color to paint the border with
  * @param shape shape of the border
  */
+@Stable
 fun Modifier.border(width: Dp, color: Color, shape: Shape = RectangleShape) =
     border(width, SolidColor(color), shape)
 
@@ -90,6 +93,7 @@
  * @param brush brush to paint the border with
  * @param shape shape of the border
  */
+@Stable
 fun Modifier.border(width: Dp, brush: Brush, shape: Shape) =
     this then BorderModifierNodeElement(width, brush, shape)
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ClipScrollableContainer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ClipScrollableContainer.kt
index d817e37..5a4da47 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ClipScrollableContainer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ClipScrollableContainer.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation
 
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.geometry.Rect
@@ -33,6 +34,7 @@
  *
  * @param orientation orientation of the scrolling
  */
+@Stable
 fun Modifier.clipScrollableContainer(orientation: Orientation) =
     then(
         if (orientation == Orientation.Vertical) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
index 0548c70..88c8618 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
@@ -21,6 +21,7 @@
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.relocation.BringIntoViewRequester
 import androidx.compose.foundation.relocation.BringIntoViewRequesterNode
+import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusEventModifierNode
 import androidx.compose.ui.focus.FocusProperties
@@ -101,6 +102,7 @@
  *
  * @sample androidx.compose.foundation.samples.FocusableFocusGroupSample
  */
+@Stable
 fun Modifier.focusGroup(): Modifier {
     return this
         .then(focusGroupInspectorInfo)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt
index a9b76b7..b2b172a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/ContentInViewNode.kt
@@ -99,7 +99,9 @@
     private var trackingFocusedChild = false
 
     /** The size of the scrollable container. */
-    private var viewportSize = IntSize.Zero
+    internal var viewportSize = IntSize.Zero
+        private set
+
     private var isAnimationRunning = false
     private val animationState =
         UpdatableAnimationState(bringIntoViewSpec.scrollAnimationSpec)
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 7a75b6b..47afcb9 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
@@ -42,6 +42,13 @@
 import androidx.compose.ui.focus.FocusPropertiesModifierNode
 import androidx.compose.ui.focus.FocusTargetModifierNode
 import androidx.compose.ui.geometry.Offset
+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.KeyInputModifierNode
+import androidx.compose.ui.input.key.isCtrlPressed
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.type
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -103,6 +110,7 @@
  * @param interactionSource [MutableInteractionSource] that will be used to emit
  * drag events when this scrollable is being dragged.
  */
+@Stable
 @OptIn(ExperimentalFoundationApi::class)
 fun Modifier.scrollable(
     state: ScrollableState,
@@ -156,6 +164,7 @@
  * Note: This API is experimental as it brings support for some experimental features:
  * [overscrollEffect] and [bringIntoViewScroller].
  */
+@Stable
 @ExperimentalFoundationApi
 fun Modifier.scrollable(
     state: ScrollableState,
@@ -267,7 +276,8 @@
     private var interactionSource: MutableInteractionSource?,
     bringIntoViewSpec: BringIntoViewSpec
 ) : DelegatingNode(), ObserverModifierNode, CompositionLocalConsumerModifierNode,
-    FocusPropertiesModifierNode {
+    FocusPropertiesModifierNode, KeyInputModifierNode {
+
     val nestedScrollDispatcher = NestedScrollDispatcher()
 
     // Place holder fling behavior, we'll initialize it when the density is available.
@@ -391,6 +401,56 @@
     override fun applyFocusProperties(focusProperties: FocusProperties) {
         focusProperties.canFocus = false
     }
+
+    // Key handler for Page up/down scrolling behavior.
+    override fun onKeyEvent(event: KeyEvent): Boolean {
+        return if (enabled &&
+            (event.key == Key.PageDown || event.key == Key.PageUp) &&
+            (event.type == KeyEventType.KeyDown) &&
+            (!event.isCtrlPressed)
+            ) {
+            with(scrollingLogic) {
+                val scrollAmount: Offset = if (orientation == Orientation.Vertical) {
+                    val viewportHeight = contentInViewNode.viewportSize.height
+
+                    val yAmount = if (event.key == Key.PageUp) {
+                        viewportHeight.toFloat()
+                    } else {
+                        -viewportHeight.toFloat()
+                    }
+
+                    Offset(0f, yAmount)
+                } else {
+                    val viewportWidth = contentInViewNode.viewportSize.width
+
+                    val xAmount = if (event.key == Key.PageUp) {
+                        viewportWidth.toFloat()
+                    } else {
+                        -viewportWidth.toFloat()
+                    }
+
+                    Offset(xAmount, 0f)
+                }
+
+                // A coroutine is launched for every individual scroll event in the
+                // larger scroll gesture. If we see degradation in the future (that is,
+                // a fast scroll gesture on a slow device causes UI jank [not seen up to
+                // this point), we can switch to a more efficient solution where we
+                // lazily launch one coroutine (with the first event) and use a Channel
+                // to communicate the scroll amount to the UI thread.
+                coroutineScope.launch {
+                    scrollableState.scroll(MutatePriority.UserInput) {
+                        dispatchScroll(scrollAmount, Wheel)
+                    }
+                }
+            }
+            true
+        } else {
+            false
+        }
+    }
+
+    override fun onPreKeyEvent(event: KeyEvent) = false
 }
 
 /**
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyGridSnapLayoutInfoProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyGridSnapLayoutInfoProvider.kt
index 236c03f..79f8886 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyGridSnapLayoutInfoProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyGridSnapLayoutInfoProvider.kt
@@ -24,11 +24,11 @@
 import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
 import androidx.compose.foundation.lazy.grid.LazyGridLayoutInfo
 import androidx.compose.foundation.lazy.grid.LazyGridState
-import androidx.compose.ui.unit.Density
 import androidx.compose.ui.util.fastFilter
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastSumBy
 import kotlin.math.absoluteValue
+import kotlin.math.floor
 import kotlin.math.sign
 
 /**
@@ -49,11 +49,18 @@
     private val layoutInfo: LazyGridLayoutInfo
         get() = lazyGridState.layoutInfo
 
-    override fun Density.calculateApproachOffset(initialVelocity: Float): Float {
-        val decayAnimationSpec: DecayAnimationSpec<Float> = splineBasedDecay(this)
+    override fun calculateApproachOffset(initialVelocity: Float): Float {
+        val decayAnimationSpec: DecayAnimationSpec<Float> = splineBasedDecay(lazyGridState.density)
         val offset =
             decayAnimationSpec.calculateTargetValue(NoDistance, initialVelocity).absoluteValue
-        val finalDecayOffset = (offset - calculateSnapStepSize()).coerceAtLeast(0f)
+
+        val estimatedNumberOfItemsInDecay = floor(offset.absoluteValue / averageItemSize())
+
+        // Decay to exactly half an item before the item where this decay would let us finish.
+        // The rest of the animation will be a snapping animation.
+        val approachOffset = estimatedNumberOfItemsInDecay * averageItemSize() - averageItemSize()
+        val finalDecayOffset = approachOffset.coerceAtLeast(0f)
+
         return if (finalDecayOffset == 0f) {
             finalDecayOffset
         } else {
@@ -71,7 +78,7 @@
         }
     }
 
-    override fun Density.calculateSnappingOffset(
+    override fun calculateSnappingOffset(
         currentVelocity: Float
     ): Float {
         var distanceFromItemBeforeTarget = Float.NEGATIVE_INFINITY
@@ -107,7 +114,7 @@
         )
     }
 
-    override fun Density.calculateSnapStepSize(): Float {
+    fun averageItemSize(): Float {
         val items = singleAxisItems()
         return if (items.isNotEmpty()) {
             val size = if (layoutInfo.orientation == Orientation.Vertical) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyListSnapLayoutInfoProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyListSnapLayoutInfoProvider.kt
index 20ad4cf..512b509 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyListSnapLayoutInfoProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyListSnapLayoutInfoProvider.kt
@@ -26,10 +26,10 @@
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
-import androidx.compose.ui.unit.Density
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastSumBy
 import kotlin.math.absoluteValue
+import kotlin.math.floor
 import kotlin.math.sign
 
 /**
@@ -52,11 +52,17 @@
         get() = lazyListState.layoutInfo
 
     // Decayed page snapping is the default
-    override fun Density.calculateApproachOffset(initialVelocity: Float): Float {
-        val decayAnimationSpec: DecayAnimationSpec<Float> = splineBasedDecay(this)
+    override fun calculateApproachOffset(initialVelocity: Float): Float {
+        val decayAnimationSpec: DecayAnimationSpec<Float> = splineBasedDecay(lazyListState.density)
         val offset =
             decayAnimationSpec.calculateTargetValue(NoDistance, initialVelocity).absoluteValue
-        val finalDecayOffset = (offset - calculateSnapStepSize()).coerceAtLeast(0f)
+
+        val estimatedNumberOfItemsInDecay = floor(offset.absoluteValue / averageItemSize())
+
+        // Decay to exactly half an item before the item where this decay would let us finish.
+        // The rest of the animation will be a snapping animation.
+        val approachOffset = estimatedNumberOfItemsInDecay * averageItemSize() - averageItemSize()
+        val finalDecayOffset = approachOffset.coerceAtLeast(0f)
         return if (finalDecayOffset == 0f) {
             finalDecayOffset
         } else {
@@ -64,7 +70,7 @@
         }
     }
 
-    override fun Density.calculateSnappingOffset(currentVelocity: Float): Float {
+    override fun calculateSnappingOffset(currentVelocity: Float): Float {
         var lowerBoundOffset = Float.NEGATIVE_INFINITY
         var upperBoundOffset = Float.POSITIVE_INFINITY
 
@@ -94,7 +100,7 @@
         return calculateFinalOffset(currentVelocity, lowerBoundOffset, upperBoundOffset)
     }
 
-    override fun Density.calculateSnapStepSize(): Float = with(layoutInfo) {
+    fun averageItemSize(): Float = with(layoutInfo) {
         if (visibleItemsInfo.isNotEmpty()) {
             visibleItemsInfo.fastSumBy { it.size } / visibleItemsInfo.size.toFloat()
         } else {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior.kt
index 4df8844..2aa4948 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapFlingBehavior.kt
@@ -38,8 +38,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import kotlin.math.abs
 import kotlin.math.absoluteValue
@@ -74,9 +72,8 @@
  * @param highVelocityAnimationSpec The animation spec used to approach the target offset. When
  * the fling velocity is large enough. Large enough means large enough to naturally decay.
  * @param snapAnimationSpec The animation spec used to finally snap to the correct bound.
- * @param density The screen [Density]
- * @param shortSnapVelocityThreshold Use the given velocity to determine if it's a
- * short or long snap.
+ * @param shortSnapVelocityThreshold Use the given velocity to determine if it's a short or long
+ * snap. The velocity should be provided in pixels/s.
  *
  */
 @ExperimentalFoundationApi
@@ -85,11 +82,9 @@
     private val lowVelocityAnimationSpec: AnimationSpec<Float>,
     private val highVelocityAnimationSpec: DecayAnimationSpec<Float>,
     private val snapAnimationSpec: AnimationSpec<Float>,
-    private val density: Density,
-    private val shortSnapVelocityThreshold: Dp = MinFlingVelocityDp
+    private val shortSnapVelocityThreshold: Float
 ) : FlingBehavior {
 
-    private val velocityThreshold = with(density) { shortSnapVelocityThreshold.toPx() }
     internal var motionScaleDuration = DefaultScrollMotionDurationScale
 
     override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
@@ -129,7 +124,7 @@
     ): AnimationResult<Float, AnimationVector1D> {
         // If snapping from scroll (short snap) or fling (long snap)
         val result = withContext(motionScaleDuration) {
-            if (abs(initialVelocity) <= abs(velocityThreshold)) {
+            if (abs(initialVelocity) <= abs(shortSnapVelocityThreshold)) {
                 shortSnap(initialVelocity, onRemainingScrollOffsetUpdate)
             } else {
                 longSnap(initialVelocity, onRemainingScrollOffsetUpdate)
@@ -145,9 +140,7 @@
         onRemainingScrollOffsetUpdate: (Float) -> Unit
     ): AnimationResult<Float, AnimationVector1D> {
         debugLog { "Short Snapping" }
-        val closestOffset = with(snapLayoutInfoProvider) {
-            density.calculateSnappingOffset(0f)
-        }
+        val closestOffset = snapLayoutInfoProvider.calculateSnappingOffset(0f)
 
         var remainingScrollOffset = closestOffset
 
@@ -169,7 +162,7 @@
     ): AnimationResult<Float, AnimationVector1D> {
         debugLog { "Long Snapping" }
         val initialOffset =
-            with(snapLayoutInfoProvider) { density.calculateApproachOffset(initialVelocity) }.let {
+            snapLayoutInfoProvider.calculateApproachOffset(initialVelocity).let {
                 abs(it) * sign(initialVelocity) // ensure offset sign is correct
             }
         var remainingScrollOffset = initialOffset
@@ -211,11 +204,7 @@
                 HighVelocityApproachAnimation(highVelocityAnimationSpec)
             } else {
                 debugLog { "Low Velocity Approach" }
-                LowVelocityApproachAnimation(
-                    lowVelocityAnimationSpec,
-                    snapLayoutInfoProvider,
-                    density
-                )
+                LowVelocityApproachAnimation(lowVelocityAnimationSpec)
             }
 
         return approach(
@@ -223,7 +212,6 @@
             initialVelocity,
             animation,
             snapLayoutInfoProvider,
-            density,
             onAnimationStep
         )
     }
@@ -236,12 +224,11 @@
         velocity: Float
     ): Boolean {
         val decayOffset = highVelocityAnimationSpec.calculateTargetValue(NoDistance, velocity)
-        val snapStepSize = with(snapLayoutInfoProvider) { density.calculateSnapStepSize() }
         debugLog {
             "Evaluating decay possibility with " +
                 "decayOffset=$decayOffset and proposed approach=$offset"
         }
-        return decayOffset.absoluteValue >= (offset.absoluteValue + snapStepSize)
+        return decayOffset.absoluteValue >= offset.absoluteValue
     }
 
     override fun equals(other: Any?): Boolean {
@@ -250,7 +237,6 @@
                 other.highVelocityAnimationSpec == this.highVelocityAnimationSpec &&
                 other.lowVelocityAnimationSpec == this.lowVelocityAnimationSpec &&
                 other.snapLayoutInfoProvider == this.snapLayoutInfoProvider &&
-                other.density == this.density &&
                 other.shortSnapVelocityThreshold == this.shortSnapVelocityThreshold
         } else {
             false
@@ -262,7 +248,6 @@
         .let { 31 * it + highVelocityAnimationSpec.hashCode() }
         .let { 31 * it + lowVelocityAnimationSpec.hashCode() }
         .let { 31 * it + snapLayoutInfoProvider.hashCode() }
-        .let { 31 * it + density.hashCode() }
         .let { 31 * it + shortSnapVelocityThreshold.hashCode() }
 }
 
@@ -287,7 +272,7 @@
             lowVelocityAnimationSpec = tween(easing = LinearEasing),
             highVelocityAnimationSpec = highVelocityApproachSpec,
             snapAnimationSpec = spring(stiffness = Spring.StiffnessMediumLow),
-            density = density
+            shortSnapVelocityThreshold = with(density) { MinFlingVelocityDp.toPx() }
         )
     }
 }
@@ -309,7 +294,6 @@
     initialVelocity: Float,
     animation: ApproachAnimation<Float, AnimationVector1D>,
     snapLayoutInfoProvider: SnapLayoutInfoProvider,
-    density: Density,
     onAnimationStep: (delta: Float) -> Unit
 ): AnimationResult<Float, AnimationVector1D> {
 
@@ -320,9 +304,8 @@
         onAnimationStep
     )
 
-    val remainingOffset = with(snapLayoutInfoProvider) {
-        density.calculateSnappingOffset(currentAnimationState.velocity)
-    }
+    val remainingOffset =
+        snapLayoutInfoProvider.calculateSnappingOffset(currentAnimationState.velocity)
 
     // will snap the remainder
     return AnimationResult(remainingOffset, currentAnimationState)
@@ -455,9 +438,7 @@
 
 @OptIn(ExperimentalFoundationApi::class)
 private class LowVelocityApproachAnimation(
-    private val lowVelocityAnimationSpec: AnimationSpec<Float>,
-    private val layoutInfoProvider: SnapLayoutInfoProvider,
-    private val density: Density
+    private val lowVelocityAnimationSpec: AnimationSpec<Float>
 ) : ApproachAnimation<Float, AnimationVector1D> {
     override suspend fun approachAnimation(
         scope: ScrollScope,
@@ -466,10 +447,7 @@
         onAnimationStep: (delta: Float) -> Unit
     ): AnimationResult<Float, AnimationVector1D> {
         val animationState = AnimationState(initialValue = 0f, initialVelocity = velocity)
-        val targetOffset =
-            (abs(offset) + with(layoutInfoProvider) { density.calculateSnapStepSize() }) * sign(
-                velocity
-            )
+        val targetOffset = offset.absoluteValue * sign(velocity)
         return with(scope) {
             animateSnap(
                 targetOffset = targetOffset,
@@ -532,6 +510,7 @@
 }
 
 private const val DEBUG = false
+
 private inline fun debugLog(generateMsg: () -> String) {
     if (DEBUG) {
         println("SnapFlingBehavior: ${generateMsg()}")
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapLayoutInfoProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapLayoutInfoProvider.kt
index 5f05d42..2de3743 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapLayoutInfoProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapLayoutInfoProvider.kt
@@ -17,21 +17,22 @@
 package androidx.compose.foundation.gestures.snapping
 
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.ui.unit.Density
 
 /**
  * Provides information about the layout that is using a SnapFlingBehavior.
  * The provider should give the following information:
- * 1) Snapping bounds, the previous and the next snap position offset.
- * 2) Snap Step Size, the minimum size that the SnapFlingBehavior can animate.
- * 3) Approach offset calculation, an offset to be consumed before snapping to a defined bound.
+ * 1) Snapping offset: The next snap position offset.
+ * 2) Approach offset: An offset to be consumed before snapping to a defined bound.
+ *
+ * In snapping, the approach offset and the snapping offset can be used to control how a snapping
+ * animation will look in a given SnappingLayout. The complete snapping animation can be split
+ * into 2 phases: Approach and Snapping. In the Approach phase, we'll use an animation to consume
+ * all of the offset provided by [calculateApproachOffset]. In the snapping phase,
+ * [SnapFlingBehavior] will use an animation to consume all of the offset
+ * provided by [calculateSnappingOffset].
  */
 @ExperimentalFoundationApi
 interface SnapLayoutInfoProvider {
-    /**
-     * The minimum offset that snapping will use to animate.(e.g. an item size)
-     */
-    fun Density.calculateSnapStepSize(): Float
 
     /**
      * Calculate the distance to navigate before settling into the next snapping bound.
@@ -39,7 +40,7 @@
      * @param initialVelocity The current fling movement velocity. You can use this tho calculate a
      * velocity based offset.
      */
-    fun Density.calculateApproachOffset(initialVelocity: Float): Float
+    fun calculateApproachOffset(initialVelocity: Float): Float
 
     /**
      * Given a target placement in a layout, the snapping offset is the next snapping position
@@ -50,5 +51,5 @@
      * @param currentVelocity The current fling movement velocity. This may change throughout the
      * fling animation.
      */
-    fun Density.calculateSnappingOffset(currentVelocity: Float): Float
+    fun calculateSnappingOffset(currentVelocity: Float): Float
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapPositionInLayout.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapPositionInLayout.kt
index f881aba..b0923d6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapPositionInLayout.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/SnapPositionInLayout.kt
@@ -17,7 +17,6 @@
 package androidx.compose.foundation.gestures.snapping
 
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.ui.unit.Density
 
 /**
  * Describes the general positioning of a given snap item in its containing layout.
@@ -30,7 +29,7 @@
      * align the item with a position within the container. As a base line, if we wanted to align
      * the start of the container and the start of the item, we would return 0 in this function.
      */
-    fun Density.position(layoutSize: Int, itemSize: Int, itemIndex: Int): Int
+    fun position(layoutSize: Int, itemSize: Int, itemIndex: Int): Int
 
     companion object {
         /**
@@ -42,7 +41,7 @@
 }
 
 @OptIn(ExperimentalFoundationApi::class)
-internal fun Density.calculateDistanceToDesiredSnapPosition(
+internal fun calculateDistanceToDesiredSnapPosition(
     mainAxisViewPortSize: Int,
     beforeContentPadding: Int,
     afterContentPadding: Int,
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 3370e21..881469e 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
@@ -373,8 +373,7 @@
                 lowVelocityAnimationSpec = lowVelocityAnimationSpec,
                 highVelocityAnimationSpec = highVelocityAnimationSpec,
                 snapAnimationSpec = snapAnimationSpec,
-                density = density,
-                shortSnapVelocityThreshold = snapVelocityThreshold
+                shortSnapVelocityThreshold = with(density) { snapVelocityThreshold.toPx() }
             )
         }
     }
@@ -496,7 +495,7 @@
             return this != Float.POSITIVE_INFINITY && this != Float.NEGATIVE_INFINITY
         }
 
-        override fun Density.calculateSnappingOffset(currentVelocity: Float): Float {
+        override fun calculateSnappingOffset(currentVelocity: Float): Float {
             var lowerBoundOffset = Float.NEGATIVE_INFINITY
             var upperBoundOffset = Float.POSITIVE_INFINITY
 
@@ -569,9 +568,7 @@
             }
         }
 
-        override fun Density.calculateSnapStepSize(): Float = layoutInfo.pageSize.toFloat()
-
-        override fun Density.calculateApproachOffset(initialVelocity: Float): Float {
+        override fun calculateApproachOffset(initialVelocity: Float): Float {
             debugLog { "Approach Velocity=$initialVelocity" }
             val effectivePageSizePx = pagerState.pageSize + pagerState.pageSpacing
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt
index 8041415..8001053 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt
@@ -38,6 +38,9 @@
  *
  * Here is a sample where part of a composable is brought into view:
  * @sample androidx.compose.foundation.samples.BringPartOfComposableIntoViewSample
+ *
+ * Note: this API is experimental while we optimise the performance and find the right API shape
+ * for it
  */
 @ExperimentalFoundationApi
 sealed interface BringIntoViewRequester {
@@ -73,6 +76,9 @@
  *
  * Here is a sample where a part of a composable is brought into view:
  * @sample androidx.compose.foundation.samples.BringPartOfComposableIntoViewSample
+ *
+ * Note: this API is experimental while we optimise the performance and find the right API shape
+ * for it
  */
 @ExperimentalFoundationApi
 fun BringIntoViewRequester(): BringIntoViewRequester {
@@ -94,6 +100,9 @@
  *     hoisted object can be used to send
  *     [bringIntoView][BringIntoViewRequester.bringIntoView] requests to parents
  *     of the current composable.
+ *
+ * Note: this API is experimental while we optimise the performance and find the right API shape
+ * for it
  */
 @Suppress("ModifierInspectorInfo")
 @ExperimentalFoundationApi
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt
index 71820cc..770c8a06 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewResponder.kt
@@ -48,6 +48,9 @@
  * @sample androidx.compose.foundation.samples.BringPartOfComposableIntoViewSample
  *
  * @see BringIntoViewRequester
+ *
+ * Note: this API is experimental while we optimise the performance and find the right API shape
+ * for it
  */
 @ExperimentalFoundationApi
 interface BringIntoViewResponder {
@@ -94,6 +97,9 @@
  * @sample androidx.compose.foundation.samples.BringIntoViewSample
  *
  * @see BringIntoViewRequester
+ *
+ * Note: this API is experimental while we optimise the performance and find the right API shape
+ * for it
  */
 @Suppress("ModifierInspectorInfo")
 @ExperimentalFoundationApi
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/SelectableGroup.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/SelectableGroup.kt
index 913d91f..b6ab655 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/SelectableGroup.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/SelectableGroup.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.selection
 
+import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.semantics.selectableGroup
 import androidx.compose.ui.semantics.semantics
@@ -26,6 +27,7 @@
  *
  * @see selectableGroup
  */
+@Stable
 fun Modifier.selectableGroup() = this.semantics {
     selectableGroup()
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicText.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicText.kt
index 95bc432..e5219f2 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicText.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/BasicText.kt
@@ -29,6 +29,7 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.graphics.ColorProducer
@@ -95,8 +96,13 @@
     val selectionRegistrar = LocalSelectionRegistrar.current
     val selectionController = if (selectionRegistrar != null) {
         val backgroundSelectionColor = LocalTextSelectionColors.current.backgroundColor
-        remember(selectionRegistrar, backgroundSelectionColor) {
+        val selectableId =
+            rememberSaveable(selectionRegistrar, saver = selectionIdSaver(selectionRegistrar)) {
+                selectionRegistrar.nextSelectableId()
+            }
+        remember(selectableId, selectionRegistrar, backgroundSelectionColor) {
             SelectionController(
+                selectableId,
                 selectionRegistrar,
                 backgroundSelectionColor
             )
@@ -184,8 +190,13 @@
     val selectionRegistrar = LocalSelectionRegistrar.current
     val selectionController = if (selectionRegistrar != null) {
         val backgroundSelectionColor = LocalTextSelectionColors.current.backgroundColor
-        remember(selectionRegistrar, backgroundSelectionColor) {
+        val selectableId =
+            rememberSaveable(selectionRegistrar, saver = selectionIdSaver(selectionRegistrar)) {
+                selectionRegistrar.nextSelectableId()
+            }
+        remember(selectableId, selectionRegistrar, backgroundSelectionColor) {
             SelectionController(
+                selectableId,
                 selectionRegistrar,
                 backgroundSelectionColor
             )
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringNode.kt
index e54ae82..57adcad 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectableTextAnnotatedStringNode.kt
@@ -55,7 +55,7 @@
     minLines: Int = DefaultMinLines,
     placeholders: List<AnnotatedString.Range<Placeholder>>? = null,
     onPlaceholderLayout: ((List<Rect?>) -> Unit)? = null,
-    private val selectionController: SelectionController? = null,
+    private var selectionController: SelectionController? = null,
     overrideColor: ColorProducer? = null
 ) : DelegatingNode(), LayoutModifierNode, DrawModifierNode, GlobalPositionAwareModifierNode {
 
@@ -147,6 +147,7 @@
                 selectionController = selectionController
             ),
         )
+        this.selectionController = selectionController
         // we always relayout when we're selectable
         invalidateMeasurement()
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.kt
index 34a9f2c9..ff34ae3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/SelectionController.kt
@@ -68,13 +68,13 @@
  */
 // This is _basically_ a Modifier.Node but moved into remember because we need to do pointerInput
 internal class SelectionController(
+    private val selectableId: Long,
     private val selectionRegistrar: SelectionRegistrar,
     private val backgroundSelectionColor: Color,
     // TODO: Move these into Modifer.element eventually
     private var params: StaticTextSelectionParams = StaticTextSelectionParams.Empty
 ) : RememberObserver {
     private var selectable: Selectable? = null
-    private val selectableId = selectionRegistrar.nextSelectableId()
 
     val modifier: Modifier = selectionRegistrar
         .makeSelectionModifier(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt
index 8fbd187..44104e34 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt
@@ -240,15 +240,20 @@
         getOffsetForPosition(previousHandlePosition, textLayoutResult)
     }
 
-    val startHandleDirection = getDirection(startPosition, bounds)
-    val endHandleDirection = getDirection(endPosition, bounds)
+    val startXHandleDirection = getXDirection(startPosition, bounds)
+    val endXHandleDirection = getXDirection(endPosition, bounds)
+
+    val startYHandleDirection = getYDirection(startPosition, bounds)
+    val endYHandleDirection = getYDirection(endPosition, bounds)
 
     appendInfo(
         selectableId = selectableId,
         rawStartHandleOffset = rawStartHandleOffset,
-        startHandleDirection = startHandleDirection,
+        startXHandleDirection = startXHandleDirection,
+        startYHandleDirection = startYHandleDirection,
         rawEndHandleOffset = rawEndHandleOffset,
-        endHandleDirection = endHandleDirection,
+        endXHandleDirection = endXHandleDirection,
+        endYHandleDirection = endYHandleDirection,
         rawPreviousHandleOffset = rawPreviousHandleOffset,
         textLayoutResult = textLayoutResult,
     )
@@ -271,7 +276,13 @@
     }
 }
 
-private fun getDirection(position: Offset, bounds: Rect): Direction = when {
+private fun getXDirection(position: Offset, bounds: Rect): Direction = when {
+    position.x < bounds.left -> Direction.BEFORE
+    position.x > bounds.right -> Direction.AFTER
+    else -> Direction.ON
+}
+
+private fun getYDirection(position: Offset, bounds: Rect): Direction = when {
     position.y < bounds.top -> Direction.BEFORE
     position.y > bounds.bottom -> Direction.AFTER
     else -> Direction.ON
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt
index 2e6d9cc..5a570fc 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt
@@ -24,6 +24,7 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.pointer.pointerInput
@@ -81,7 +82,10 @@
     onSelectionChange: (Selection?) -> Unit,
     children: @Composable () -> Unit
 ) {
-    val registrarImpl = remember { SelectionRegistrarImpl() }
+    val registrarImpl = rememberSaveable(saver = SelectionRegistrarImpl.Saver) {
+        SelectionRegistrarImpl()
+    }
+
     val manager = remember { SelectionManager(registrarImpl) }
 
     manager.hapticFeedBack = LocalHapticFeedback.current
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionLayout.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionLayout.kt
index f7c4e2c..424134d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionLayout.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionLayout.kt
@@ -352,7 +352,7 @@
  * @param rawStartHandleOffset the index of the start handle
  * @param rawEndHandleOffset the index of the end handle
  * @param rawPreviousHandleOffset the previous handle offset based on [isStartHandle],
- * or -1 if none
+ * or [UNASSIGNED_SLOT] if none
  * @param previousSelectionRange the previous selection
  * @param isStartOfSelection whether this is the start of a selection gesture (no previous context)
  * @param isStartHandle whether this is the start or end anchor
@@ -404,6 +404,9 @@
     COLLAPSED
 }
 
+/** Slot has not been assigned yet */
+internal const val UNASSIGNED_SLOT = -1
+
 /**
  * A builder for [SelectionLayout] that ensures the data structures and slots
  * are properly constructed.
@@ -426,9 +429,9 @@
 ) {
     private val selectableIdToInfoListIndex: MutableMap<Long, Int> = mutableMapOf()
     private val infoList: MutableList<SelectableInfo> = mutableListOf()
-    private var startSlot: Int = -1
-    private var endSlot: Int = -1
-    private var currentSlot: Int = -1
+    private var startSlot: Int = UNASSIGNED_SLOT
+    private var endSlot: Int = UNASSIGNED_SLOT
+    private var currentSlot: Int = UNASSIGNED_SLOT
 
     /**
      * Finishes building the [SelectionLayout] and returns it.
@@ -452,8 +455,8 @@
                 MultiSelectionLayout(
                     selectableIdToInfoListIndex = selectableIdToInfoListIndex,
                     infoList = infoList,
-                    startSlot = if (startSlot == -1) lastSlot else startSlot,
-                    endSlot = if (endSlot == -1) lastSlot else endSlot,
+                    startSlot = if (startSlot == UNASSIGNED_SLOT) lastSlot else startSlot,
+                    endSlot = if (endSlot == UNASSIGNED_SLOT) lastSlot else endSlot,
                     isStartHandle = isStartHandle,
                     previousSelection = previousSelection,
                 )
@@ -467,9 +470,11 @@
     fun appendInfo(
         selectableId: Long,
         rawStartHandleOffset: Int,
-        startHandleDirection: Direction,
+        startXHandleDirection: Direction,
+        startYHandleDirection: Direction,
         rawEndHandleOffset: Int,
-        endHandleDirection: Direction,
+        endXHandleDirection: Direction,
+        endYHandleDirection: Direction,
         rawPreviousHandleOffset: Int,
         textLayoutResult: TextLayoutResult,
     ): SelectableInfo {
@@ -486,41 +491,78 @@
             textLayoutResult = textLayoutResult,
         )
 
-        if (startSlot == -1) {
-            startSlot = updateBasedOnDirection(startHandleDirection)
-        }
-
-        if (endSlot == -1) {
-            endSlot = updateBasedOnDirection(endHandleDirection)
-        }
-
+        startSlot = updateSlot(startSlot, startXHandleDirection, startYHandleDirection)
+        endSlot = updateSlot(endSlot, endXHandleDirection, endYHandleDirection)
         selectableIdToInfoListIndex[selectableId] = infoList.size
         infoList += selectableInfo
         return selectableInfo
     }
 
-    private fun updateBasedOnDirection(direction: Direction): Int = when (direction) {
-        Direction.BEFORE -> currentSlot - 1
-        Direction.ON -> currentSlot
-        else -> -1
+    /**
+     * Find the slot for a selectable given the current position's directions from the selectable.
+     *
+     * The selectables must be ordered in the order in which they would be selected, and then
+     * this function should be called for each of those selectables.
+     *
+     * It is expected that the input [slot] is also assigned the result of this function.
+     *
+     * This function is stateful.
+     *
+     * @param slot the current value of this slot.
+     * @param xPositionDirection Where the x-position is relative to the selectable
+     * @param yPositionDirection Where the y-position is relative to the selectable
+     */
+    private fun updateSlot(
+        slot: Int,
+        xPositionDirection: Direction,
+        yPositionDirection: Direction,
+    ): Int {
+        if (slot != UNASSIGNED_SLOT) {
+            // don't overwrite if the slot has already been determined
+            return slot
+        }
+
+        // slot has not been determined yet,
+        // see if we are on or past the selectable we are looking for
+        return when (yPositionDirection) {
+            // If we get here, that means we never found a selectable that intersects our gesture
+            // position on the y-axis. This is the first selectable that is after the position,
+            // so our slot must be between the previous and current selectables.
+            Direction.BEFORE -> currentSlot - 1
+
+            // The gesture position intersects the bounds of the selectable in the y-axis.
+            // Now, search along the x-axis.
+            Direction.ON -> {
+                when (xPositionDirection) {
+                    // Same logic as BEFORE above, but along the x-axis.
+                    Direction.BEFORE -> currentSlot - 1
+
+                    // The gesture position is directly on this selectable, so use this one.
+                    Direction.ON -> currentSlot
+
+                    // keep looking
+                    Direction.AFTER -> slot
+                }
+            }
+
+            // keep looking
+            Direction.AFTER -> slot
+        }
     }
 }
 
 /**
  * Where the position of a cursor/press is compared to a selectable.
  */
-@JvmInline
-internal value class Direction(val direction: Int) {
-    companion object {
-        /** The cursor/press is before the selectable */
-        val BEFORE = Direction(-1)
+internal enum class Direction {
+    /** The cursor/press is before the selectable */
+    BEFORE,
 
-        /** The cursor/press is on the selectable */
-        val ON = Direction(0)
+    /** The cursor/press is on the selectable */
+    ON,
 
-        /** The cursor/press is after the selectable */
-        val AFTER = Direction(1)
-    }
+    /** The cursor/press is after the selectable */
+    AFTER
 }
 
 /**
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt
index 907c15f..dce40ef 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt
@@ -19,11 +19,23 @@
 import androidx.compose.foundation.AtomicLong
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.Saver
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.layout.LayoutCoordinates
 
-internal class SelectionRegistrarImpl : SelectionRegistrar {
+internal class SelectionRegistrarImpl private constructor(
+    initialIncrementId: Long
+) : SelectionRegistrar {
+    companion object {
+        val Saver = Saver<SelectionRegistrarImpl, Long>(
+            save = { it.incrementId.get() },
+            restore = { SelectionRegistrarImpl(it) }
+        )
+    }
+
+    constructor() : this(initialIncrementId = 1L)
+
     /**
      * A flag to check if the [Selectable]s have already been sorted.
      */
@@ -54,7 +66,7 @@
      * denote an invalid id.
      * @see SelectionRegistrar.InvalidSelectableId
      */
-    private var incrementId = AtomicLong(1)
+    private var incrementId = AtomicLong(initialIncrementId)
 
     /**
      * The callback to be invoked when the position change was triggered.
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt
index 4ec59e2..5b9e34f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt
@@ -38,6 +38,7 @@
 import androidx.compose.foundation.text.textFieldMinSize
 import androidx.compose.foundation.text2.input.CodepointTransformation
 import androidx.compose.foundation.text2.input.InputTransformation
+import androidx.compose.foundation.text2.input.SingleLineCodepointTransformation
 import androidx.compose.foundation.text2.input.TextFieldLineLimits
 import androidx.compose.foundation.text2.input.TextFieldLineLimits.MultiLine
 import androidx.compose.foundation.text2.input.TextFieldLineLimits.SingleLine
@@ -46,6 +47,7 @@
 import androidx.compose.foundation.text2.input.internal.TextFieldDecoratorModifier
 import androidx.compose.foundation.text2.input.internal.TextFieldTextLayoutModifier
 import androidx.compose.foundation.text2.input.internal.TextLayoutState
+import androidx.compose.foundation.text2.input.internal.TransformedTextFieldState
 import androidx.compose.foundation.text2.input.internal.selection.TextFieldSelectionHandle2
 import androidx.compose.foundation.text2.input.internal.selection.TextFieldSelectionState
 import androidx.compose.foundation.text2.input.internal.syncTextFieldState
@@ -448,11 +450,19 @@
     val isFocused = interactionSource.collectIsFocusedAsState().value
     val textLayoutState = remember { TextLayoutState() }
 
-    val textFieldSelectionState = remember(state, textLayoutState) {
+    val transformedState = remember(state, inputTransformation, codepointTransformation) {
+        // First prefer provided codepointTransformation if not null, e.g. BasicSecureTextField
+        // would send PasswordTransformation. Second, apply a SingleLineCodepointTransformation if
+        // text field is configured to be single line. Else, don't apply any visual transformation.
+        val appliedCodepointTransformation = codepointTransformation
+            ?: SingleLineCodepointTransformation.takeIf { singleLine }
+        TransformedTextFieldState(state, inputTransformation, appliedCodepointTransformation)
+    }
+
+    val textFieldSelectionState = remember(transformedState) {
         TextFieldSelectionState(
-            textFieldState = state,
+            textFieldState = transformedState,
             textLayoutState = textLayoutState,
-            inputTransformation = inputTransformation,
             density = density,
             editable = enabled && !readOnly,
             isFocused = isFocused
@@ -468,7 +478,6 @@
             hapticFeedBack = currentHapticFeedback,
             clipboardManager = currentClipboardManager,
             textToolbar = currentTextToolbar,
-            inputTransformation = inputTransformation,
             density = density,
             editable = enabled && !readOnly,
         )
@@ -484,7 +493,7 @@
         .then(
             // semantics + some focus + input session + touch to focus
             TextFieldDecoratorModifier(
-                textFieldState = state,
+                textFieldState = transformedState,
                 textLayoutState = textLayoutState,
                 textFieldSelectionState = textFieldSelectionState,
                 filter = inputTransformation,
@@ -534,7 +543,7 @@
                         TextFieldCoreModifier(
                             isFocused = isFocused,
                             textLayoutState = textLayoutState,
-                            textFieldState = state,
+                            textFieldState = transformedState,
                             textFieldSelectionState = textFieldSelectionState,
                             cursorBrush = cursorBrush,
                             writeable = enabled && !readOnly,
@@ -546,8 +555,7 @@
                 Box(
                     modifier = TextFieldTextLayoutModifier(
                         textLayoutState = textLayoutState,
-                        textFieldState = state,
-                        codepointTransformation = codepointTransformation,
+                        textFieldState = transformedState,
                         textStyle = textStyle,
                         singleLine = singleLine,
                         onTextLayout = onTextLayout
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/CodepointTransformation.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/CodepointTransformation.kt
index 7dd443e..81efc4c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/CodepointTransformation.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/CodepointTransformation.kt
@@ -18,6 +18,9 @@
 
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.text.appendCodePointX
+import androidx.compose.foundation.text2.input.internal.OffsetMappingCalculator
+import androidx.compose.foundation.text2.input.internal.charCount
+import androidx.compose.foundation.text2.input.internal.codePointAt
 import androidx.compose.runtime.Stable
 
 /**
@@ -84,17 +87,36 @@
 }
 
 @OptIn(ExperimentalFoundationApi::class)
-internal fun CharSequence.toVisualText(
-    codepointTransformation: CodepointTransformation?
+internal fun TextFieldCharSequence.toVisualText(
+    codepointTransformation: CodepointTransformation,
+    offsetMappingCalculator: OffsetMappingCalculator
 ): CharSequence {
-    codepointTransformation ?: return this
     val text = this
-    return buildString {
-        (0 until Character.codePointCount(text, 0, text.length)).forEach { codepointIndex ->
-            val codepoint = codepointTransformation.transform(
-                codepointIndex, Character.codePointAt(text, codepointIndex)
-            )
-            appendCodePointX(codepoint)
+    var changed = false
+    val newText = buildString {
+        var charOffset = 0
+        var codePointOffset = 0
+        while (charOffset < text.length) {
+            val codePoint = text.codePointAt(charOffset)
+            val newCodePoint = codepointTransformation.transform(codePointOffset, codePoint)
+            val charCount = charCount(codePoint)
+            if (newCodePoint != codePoint) {
+                changed = true
+                val newCharCount = charCount(newCodePoint)
+                offsetMappingCalculator.recordEditOperation(
+                    sourceStart = length,
+                    sourceEnd = length + charCount,
+                    newLength = newCharCount
+                )
+            }
+            appendCodePointX(newCodePoint)
+
+            charOffset += charCount
+            codePointOffset += 1
         }
     }
+
+    // Return the same instance if nothing changed, which signals to the caller that nothing changed
+    // and allows the new string to be GC'd earlier.
+    return if (changed) newText else this
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldBuffer.kt
index 10ac37e..a332068 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldBuffer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldBuffer.kt
@@ -150,7 +150,7 @@
     ) {
         require(start <= end) { "Expected start=$start <= end=$end" }
         require(textStart <= textEnd) { "Expected textStart=$textStart <= textEnd=$textEnd" }
-        onTextWillChange(TextRange(start, end), textEnd - textStart)
+        onTextWillChange(start, end, textEnd - textStart)
         buffer.replace(start, end, text, textStart, textEnd)
     }
 
@@ -168,7 +168,7 @@
     // This append overload should be first so it ends up being the target of links to this method.
     override fun append(text: CharSequence?): Appendable = apply {
         if (text != null) {
-            onTextWillChange(TextRange(length), text.length)
+            onTextWillChange(length, length, text.length)
             buffer.replace(buffer.length, buffer.length, text)
         }
     }
@@ -176,30 +176,31 @@
     // Doc inherited from Appendable.
     override fun append(text: CharSequence?, start: Int, end: Int): Appendable = apply {
         if (text != null) {
-            onTextWillChange(TextRange(length), end - start)
+            onTextWillChange(length, length, end - start)
             buffer.replace(buffer.length, buffer.length, text.subSequence(start, end))
         }
     }
 
     // Doc inherited from Appendable.
     override fun append(char: Char): Appendable = apply {
-        onTextWillChange(TextRange(length), 1)
+        onTextWillChange(length, length, 1)
         buffer.replace(buffer.length, buffer.length, char.toString())
     }
 
     /**
      * Called just before the text contents are about to change.
      *
-     * @param rangeToBeReplaced The range in the current text that's about to be replaced.
+     * @param replaceStart The first offset to be replaced (inclusive).
+     * @param replaceEnd The last offset to be replaced (exclusive).
      * @param newLength The length of the replacement.
      */
-    private fun onTextWillChange(rangeToBeReplaced: TextRange, newLength: Int) {
+    private fun onTextWillChange(replaceStart: Int, replaceEnd: Int, newLength: Int) {
         (changeTracker ?: ChangeTracker().also { changeTracker = it })
-            .trackChange(rangeToBeReplaced, newLength)
+            .trackChange(replaceStart, replaceEnd, newLength)
 
         // Adjust selection.
-        val start = rangeToBeReplaced.min
-        val end = rangeToBeReplaced.max
+        val start = minOf(replaceStart, replaceEnd)
+        val end = maxOf(replaceStart, replaceEnd)
         var selStart = selectionInChars.min
         var selEnd = selectionInChars.max
 
@@ -448,7 +449,7 @@
     /**
      * The ordered list of non-overlapping and discontinuous changes performed on a
      * [TextFieldBuffer] during the current [edit][TextFieldState.edit] or
-     * [filter][TextEditFilter.filter] operation. Changes are listed in the order they appear in the
+     * [filter][InputTransformation.transformInput] operation. Changes are listed in the order they appear in the
      * text, not the order in which they were made. Overlapping changes are represented as a single
      * change.
      */
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldState.kt
index f234dc8..1ee0053 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextFieldState.kt
@@ -460,12 +460,3 @@
     textAsFlow().collectLatest(block)
     error("textAsFlow expected not to complete without exception")
 }
-
-@OptIn(ExperimentalFoundationApi::class)
-internal fun TextFieldState.deselect() {
-    if (!text.selectionInChars.collapsed) {
-        edit {
-            selectCharsIn(TextRange(text.selectionInChars.max))
-        }
-    }
-}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTracker.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTracker.kt
index a3f31a5..ad73c39 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTracker.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTracker.kt
@@ -64,15 +64,17 @@
      *     for the new text.
      *  3. Offset all remaining changes are to account for the new text.
      */
-    fun trackChange(preRange: TextRange, postLength: Int) {
-        if (preRange.collapsed && postLength == 0) {
+    fun trackChange(preStart: Int, preEnd: Int, postLength: Int) {
+        if (preStart == preEnd && postLength == 0) {
             // Ignore noop changes.
             return
         }
+        val preMin = minOf(preStart, preEnd)
+        val preMax = maxOf(preStart, preEnd)
 
         var i = 0
         var recordedNewChange = false
-        val postDelta = postLength - preRange.length
+        val postDelta = postLength - (preMax - preStart)
 
         var mergedOverlappingChange: Change? = null
         while (i < _changes.size) {
@@ -80,8 +82,8 @@
 
             // Merge adjacent and overlapping changes as we go.
             if (
-                change.preStart in preRange.min..preRange.max ||
-                change.preEnd in preRange.min..preRange.max
+                change.preStart in preMin..preMax ||
+                change.preEnd in preMin..preMax
             ) {
                 if (mergedOverlappingChange == null) {
                     mergedOverlappingChange = change
@@ -94,10 +96,10 @@
                 continue
             }
 
-            if (change.preStart > preRange.max && !recordedNewChange) {
+            if (change.preStart > preMax && !recordedNewChange) {
                 // First non-overlapping change after the new one – record the change before
                 // proceeding.
-                appendNewChange(mergedOverlappingChange, preRange, postDelta)
+                appendNewChange(mergedOverlappingChange, preMin, preMax, postDelta)
                 recordedNewChange = true
             }
 
@@ -112,7 +114,7 @@
         if (!recordedNewChange) {
             // The new change is after or overlapping all previous changes so it hasn't been
             // appended yet.
-            appendNewChange(mergedOverlappingChange, preRange, postDelta)
+            appendNewChange(mergedOverlappingChange, preMin, preMax, postDelta)
         }
 
         // Swap the lists.
@@ -146,7 +148,8 @@
 
     private fun appendNewChange(
         mergedOverlappingChange: Change?,
-        preRange: TextRange,
+        preMin: Int,
+        preMax: Int,
         postDelta: Int
     ) {
         var originalDelta = if (_changesTemp.isEmpty()) 0 else {
@@ -155,11 +158,11 @@
         val newChange: Change
         if (mergedOverlappingChange == null) {
             // There were no overlapping changes, so allocate a new one.
-            val originalStart = preRange.min - originalDelta
-            val originalEnd = originalStart + preRange.length
+            val originalStart = preMin - originalDelta
+            val originalEnd = originalStart + (preMax - preMin)
             newChange = Change(
-                preStart = preRange.min,
-                preEnd = preRange.max + postDelta,
+                preStart = preMin,
+                preEnd = preMax + postDelta,
                 originalStart = originalStart,
                 originalEnd = originalEnd
             )
@@ -167,16 +170,16 @@
             newChange = mergedOverlappingChange
             // Convert the merged overlapping changes to the `post` space.
             // Merge the new changed with the merged overlapping changes.
-            if (newChange.preStart > preRange.min) {
+            if (newChange.preStart > preMin) {
                 // The new change starts before the merged overlapping set.
-                newChange.preStart = preRange.min
-                newChange.originalStart = preRange.min
+                newChange.preStart = preMin
+                newChange.originalStart = preMin
             }
-            if (preRange.max > newChange.preEnd) {
+            if (preMax > newChange.preEnd) {
                 // The new change ends after the merged overlapping set.
                 originalDelta = newChange.preEnd - newChange.originalEnd
-                newChange.preEnd = preRange.max
-                newChange.originalEnd = preRange.max - originalDelta
+                newChange.preEnd = preMax
+                newChange.originalEnd = preMax - originalDelta
             }
             newChange.preEnd += postDelta
         }
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/CodepointHelpers.kt
similarity index 66%
copy from camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
copy to compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/CodepointHelpers.kt
index 7a355f8..7fd39aa 100644
--- a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/CodepointHelpers.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.camera.effects;
+package androidx.compose.foundation.text2.input.internal
 
-import androidx.annotation.RestrictTo;
-
-/**
- * Provides a portrait post-processing effect.
- *
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class Portrait {
-    // TODO: implement this
-}
+internal expect fun CharSequence.codePointAt(index: Int): Int
+internal expect fun CharSequence.codePointCount(): Int
+internal expect fun charCount(codePoint: Int): Int
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/EditingBuffer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/EditingBuffer.kt
index 49b22ae..d869ec5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/EditingBuffer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/EditingBuffer.kt
@@ -138,10 +138,6 @@
         checkRange(selection.start, selection.end)
     }
 
-    fun replace(start: Int, end: Int, text: AnnotatedString) {
-        replace(start, end, text.text)
-    }
-
     /**
      * Replace the text and move the cursor to the end of inserted text.
      *
@@ -150,12 +146,12 @@
      * @throws IndexOutOfBoundsException if start or end offset is outside of current buffer
      * @throws IllegalArgumentException if start is larger than end. (reversed range)
      */
-    fun replace(start: Int, end: Int, text: String) {
+    fun replace(start: Int, end: Int, text: CharSequence) {
         checkRange(start, end)
         val min = minOf(start, end)
         val max = maxOf(start, end)
 
-        changeTracker.trackChange(TextRange(start, end), text.length)
+        changeTracker.trackChange(start, end, text.length)
 
         gapBuffer.replace(min, max, text)
 
@@ -184,7 +180,7 @@
         checkRange(start, end)
         val deleteRange = TextRange(start, end)
 
-        changeTracker.trackChange(deleteRange, 0)
+        changeTracker.trackChange(start, end, 0)
 
         gapBuffer.replace(deleteRange.min, deleteRange.max, "")
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/OffsetMappingCalculator.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/OffsetMappingCalculator.kt
new file mode 100644
index 0000000..6c9ebb7
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/OffsetMappingCalculator.kt
@@ -0,0 +1,415 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text2.input.internal
+
+import androidx.compose.ui.text.TextRange
+
+/**
+ * Builds up bidirectional mapping functions to map offsets from an original string to corresponding
+ * in a string that has some edit operations applied.
+ *
+ * Edit operations must be reported via [recordEditOperation]. Offsets can be mapped
+ * [from the source string][mapFromSource] and [back to the source][mapFromDest]. Mapping between
+ * source and transformed offsets is a symmetric operation – given a sequence of edit operations,
+ * the result of mapping each offset from the source to transformed strings will be the same as
+ * mapping backwards on the inverse sequence of edit operations.
+ *
+ * By default, offsets map one-to-one. However, around an edit operation, some alternative rules
+ * apply. In general, offsets are mapped one-to-one where they can be unambiguously. When there
+ * isn't enough information, the mapping is ambiguous, and the mapping result will be a [TextRange]
+ * instead of a single value, where the range represents all the possible offsets that it could map
+ * to.
+ *
+ * _Note: In the following discussion, `I` refers to the start offset in the source text, `N` refers
+ * to the length of the range in the source text, and `N'` refers to the length of the replacement
+ * text. So given `"abc"` and replacing `"bc"` with `"xyz"`, `I=1`, `N=2`, and `N'=3`._
+ *
+ * ### Insertions
+ * When text is inserted (i.e. zero-length range is replaced with non-zero-length range), the
+ * mapping is ambiguous because all of the offsets in the inserted text map back to the same offset
+ * in the source text - the offset where the text was inserted. That means the insertion point can
+ * map to any of the offsets in the inserted text. I.e. I -> I..N'
+ *
+ * - This is slightly different than the replacement case, because the offset of the start of
+ *   the operation and the offset of the end of the operation (which are the same) map to a
+ *   range instead of a scalar. This is because there is not enough information to map the start
+ *   and end offsets 1-to-1 to offsets in the transformed text.
+ * - This is symmetric with deletion: Mapping backward from an insertion is the same as mapping
+ *   forward from a deletion.
+ *
+ * ### Deletions
+ * In the inverse case, when text is deleted, mapping is unambiguous. All the offsets in the
+ * deleted range map to the start of the deleted range. I.e. I..N -> I
+ *
+ * - This is symmetric with insertion: Mapping backward from a deletion is the same as mapping
+ *   forward from an insertion.
+ *
+ * ### Replacements
+ * When text is replaced, there are both ambiguous and unambiguous mappings:
+ * - The offsets at the _ends_ of the replaced range map unambiguously to the offsets at the
+ *   corresponding edges of the replaced text. I -> I and I+1 -> I+N
+ * - The offsets _inside_ the replaced range (exclusive) map ambiguously to the entire replaced
+ *   range. I+1..I+N-1 -> I+1..I+N'-1
+ * - Note that this means that when a string with length >1 is replaced by a single character,
+ *   all the offsets inside that string will map not to the index of the replacement character
+ *   but to a single-char _range_ containing that character.
+ *
+ * ### Examples
+ *
+ * #### Inserting text
+ * ```
+ *     012
+ * A: "ab"
+ *     | \
+ *     |  \
+ * B: "azzzb"
+ *     012345
+ * ```
+ *
+ * Forward mapping:
+ *
+ * | from A: | 0 | 1 | 2 |
+ * |--------:|:-:|:-:|:-:|
+ * |   to B: | 0 |1-4| 5 |
+ *
+ * Reverse mapping:
+ *
+ * | from B: | 0 | 1 | 2 | 3 | 4 | 5 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|:-:|
+ * |   to A: | 0 | 1 | 1 | 1 | 1 | 2 |
+ *
+ * #### Deleting text
+ * ```
+ *     012345
+ * A: "azzzb"
+ *     |  /
+ *     | /
+ * B: "ab"
+ *     012
+ * ```
+ *
+ * Forward mapping:
+ *
+ * | from A: | 0 | 1 | 2 | 3 | 4 | 5 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|:-:|
+ * |   to B: | 0 | 1 | 1 | 1 | 1 | 2 |
+ *
+ * Reverse mapping:
+ *
+ * | from B: | 0 | 1 | 2 |
+ * |--------:|:-:|:-:|:-:|
+ * |   to A: | 0 |1-4| 5 |
+ *
+ * #### Replacing text: single char with char
+ * ```
+ *     0123
+ * A: "abc"
+ *      |
+ *      |
+ * B: "azc"
+ *     0123
+ * ```
+ *
+ * Forward/reverse mapping: identity
+ *
+ * | from: | 0 | 1 | 2 | 3 |
+ * |------:|:-:|:-:|:-:|:-:|
+ * |   to: | 0 | 1 | 2 | 3 |
+ *
+ * #### Replacing text: char with chars
+ * ```
+ *     0123
+ * A: "abc"
+ *      |
+ *      |\
+ * B: "azzc"
+ *     01234
+ * ```
+ *
+ * Forward mapping:
+ *
+ * | from A: | 0 | 1 | 2 | 3 |
+ * |--------:|:-:|:-:|:-:|:-:|
+ * |   to B: | 0 | 1 | 3 | 4 |
+ *
+ * Reverse mapping:
+ *
+ * | from B: | 0 | 1 | 2 | 3 | 4 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|
+ * |   to A: | 0 | 1 | 1 | 2 | 3 |
+ *
+ * #### Replacing text: chars with chars
+ * ```
+ *     012345
+ * A: "abcde"
+ *      | |
+ *      | /
+ * B: "azze"
+ *     01234
+ * ```
+ *
+ * Forward mapping:
+ *
+ * | from A: | 0 | 1 | 2 | 3 | 4 | 5 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|:-:|
+ * |   to B: | 0 | 1 |1-3|1-3| 3 | 4 |
+ *
+ * Reverse mapping:
+ *
+ * | from B: | 0 | 1 | 2 | 3 | 4 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|
+ * |   to A: | 0 | 1 |1-4| 4 | 5 |
+ *
+ * ### Multiple operations
+ *
+ * While the above apply to single edit operations, when multiple edit operations are recorded the
+ * same rules apply. The rules are applied to the first operation, then the result of that is
+ * effectively used as the input text for the next operation, etc. Because an offset can map to a
+ * range at each step, we track both a start and end offset (which start as the same value), and
+ * at each step combine the start and end _ranges_ by taking their union.
+ *
+ * #### Multiple char-to-char replacements (codepoint transformation):
+ * ```
+ *     0123
+ * A: "abc"
+ *     |
+ *    "•bc"
+ *      |
+ *    "••c"
+ *       |
+ * B: "•••"
+ *     0123
+ * ```
+ *
+ * Forward/reverse mapping: identity
+ *
+ * | from: | 0 | 1 | 2 | 3 |
+ * |------:|:-:|:-:|:-:|:-:|
+ * |   to: | 0 | 1 | 2 | 3 |
+ *
+ * #### Multiple inserts:
+ * ```
+ *     01234
+ * A: "abcd"
+ *      |
+ *    "a(bcd"
+ *         |
+ * B: "a(bc)d"
+ *     0123456
+ * ```
+ *
+ * Forward mapping:
+ *
+ * | from A: | 0 | 1 | 2 | 3 | 4 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|
+ * |   to B: | 0 |1-2| 3 |4-5| 6 |
+ *
+ * Reverse mapping:
+ *
+ * | from B: | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
+ * |   to A: | 0 | 1 | 1 | 2 | 3 | 3 | 4 |
+ *
+ * #### Multiple replacements of the same range:
+ * ```
+ *     01234
+ * A: "abcd"
+ *      ||
+ *    "awxd"
+ *      ||
+ * B: "ayzd"
+ *     01234
+ * ```
+ *
+ * Forward mapping:
+ *
+ * | from A: | 0 | 1 | 2 | 3 | 4 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|
+ * |   to B: | 0 | 1 |1-3| 3 | 4 |
+ *
+ * Reverse mapping:
+ *
+ * | from B: | 0 | 1 | 2 | 3 | 4 |
+ * |--------:|:-:|:-:|:-:|:-:|:-:|
+ * |   to A: | 0 | 1 |1-3| 3 | 4 |
+ *
+ * For other edge cases, including overlapping replacements, see `OffsetMappingCalculatorTest`.
+ */
+internal class OffsetMappingCalculator {
+    /** Resizable array of edit operations, size is defined by [opsSize]. */
+    private var ops = OpArray(size = 10)
+    private var opsSize = 0
+
+    /**
+     * Records an edit operation that replaces the range from [sourceStart] (inclusive) to
+     * [sourceEnd] (exclusive) in the original text with some text with length [newLength].
+     */
+    fun recordEditOperation(sourceStart: Int, sourceEnd: Int, newLength: Int) {
+        require(newLength >= 0) { "Expected newLen to be ≥ 0, was $newLength" }
+        val sourceMin = minOf(sourceStart, sourceEnd)
+        val sourceMax = maxOf(sourceMin, sourceEnd)
+        val sourceLength = sourceMax - sourceMin
+
+        // 0,0 is a noop, and 1,1 is always a 1-to-1 mapping so we don't need to record it.
+        if (sourceLength < 2 && sourceLength == newLength) return
+
+        // Append the operation to the array, growing it if necessary.
+        val newSize = opsSize + 1
+        if (newSize > ops.size) {
+            val newCapacity = maxOf(newSize * 2, ops.size * 2)
+            ops = ops.copyOf(newCapacity)
+        }
+        ops.set(opsSize, sourceMin, sourceLength, newLength)
+        opsSize = newSize
+    }
+
+    /**
+     * Maps an [offset] in the original string to the corresponding offset, or range of offsets,
+     * in the transformed string.
+     */
+    fun mapFromSource(offset: Int): TextRange = map(offset, fromSource = true)
+
+    /**
+     * Maps an [offset] in the original string to the corresponding offset, or range of offsets,
+     * in the transformed string.
+     */
+    fun mapFromDest(offset: Int): TextRange = map(offset, fromSource = false)
+
+    private fun map(offset: Int, fromSource: Boolean): TextRange {
+        var start = offset
+        var end = offset
+
+        // This algorithm works for both forward and reverse mapping, we just need to iterate
+        // backwards to do reverse mapping.
+        ops.forEach(max = opsSize, reversed = !fromSource) { opOffset, opSrcLen, opDestLen ->
+            val newStart = mapStep(
+                offset = start,
+                opOffset = opOffset,
+                untransformedLen = opSrcLen,
+                transformedLen = opDestLen,
+                fromSource = fromSource
+            )
+            val newEnd = mapStep(
+                offset = end,
+                opOffset = opOffset,
+                untransformedLen = opSrcLen,
+                transformedLen = opDestLen,
+                fromSource = fromSource
+            )
+            // range = newStart ∪ newEnd
+            // Note we don't read TextRange.min/max here because the above code always returns
+            // normalized ranges. It's no less correct, but there's no need to do the additional
+            // min/max calls inside the min/max properties.
+            start = minOf(newStart.start, newEnd.start)
+            end = maxOf(newStart.end, newEnd.end)
+        }
+
+        return TextRange(start, end)
+    }
+
+    private fun mapStep(
+        offset: Int,
+        opOffset: Int,
+        untransformedLen: Int,
+        transformedLen: Int,
+        fromSource: Boolean
+    ): TextRange {
+        val srcLen = if (fromSource) untransformedLen else transformedLen
+        val destLen = if (fromSource) transformedLen else untransformedLen
+        return when {
+            // Before the operation, no change.
+            offset < opOffset -> TextRange(offset)
+
+            offset == opOffset -> {
+                if (srcLen == 0) {
+                    // On insertion point, map to inserted range.
+                    TextRange(opOffset, opOffset + destLen)
+                } else {
+                    // On start of replacement, map to start of replaced range.
+                    TextRange(opOffset)
+                }
+            }
+
+            offset < opOffset + srcLen -> {
+                if (destLen == 0) {
+                    // In deleted range, map to start of deleted range.
+                    TextRange(opOffset)
+                } else {
+                    // In replaced range, map to transformed range.
+                    TextRange(opOffset, opOffset + destLen)
+                }
+            }
+
+            // On end of or after replaced range, offset the offset.
+            else -> TextRange(offset - srcLen + destLen)
+        }
+    }
+}
+
+/**
+ * An array of 3-tuples of ints. Each element's values are stored as individual values in the
+ * underlying array.
+ */
+@kotlin.jvm.JvmInline
+private value class OpArray private constructor(private val values: IntArray) {
+    constructor(size: Int) : this(IntArray(size * ElementSize))
+
+    val size: Int get() = values.size / ElementSize
+
+    fun set(index: Int, offset: Int, srcLen: Int, destLen: Int) {
+        values[index * ElementSize] = offset
+        values[index * ElementSize + 1] = srcLen
+        values[index * ElementSize + 2] = destLen
+    }
+
+    fun copyOf(newSize: Int) = OpArray(values.copyOf(newSize * ElementSize))
+
+    /**
+     * Loops through the array between 0 and [max] (exclusive). If [reversed] is false (the
+     * default), iterates forward from 0. When it's true, iterates backwards from `max-1`.
+     */
+    inline fun forEach(
+        max: Int,
+        reversed: Boolean = false,
+        block: (offset: Int, srcLen: Int, destLen: Int) -> Unit
+    ) {
+        if (max < 0) return
+        // Note: This stamps out block twice at the callsite, which is normally bad for an inline
+        // function to do. However, this is a file-private function which is only called in one
+        // place that would need to have two copies of mostly-identical code anyway. Doing the
+        // duplication here keeps the more complicated logic at the callsite more readable.
+        if (reversed) {
+            for (i in max - 1 downTo 0) {
+                val offset = values[i * ElementSize]
+                val srcLen = values[i * ElementSize + 1]
+                val destLen = values[i * ElementSize + 2]
+                block(offset, srcLen, destLen)
+            }
+        } else {
+            for (i in 0 until max) {
+                val offset = values[i * ElementSize]
+                val srcLen = values[i * ElementSize + 1]
+                val destLen = values[i * ElementSize + 2]
+                block(offset, srcLen, destLen)
+            }
+        }
+    }
+
+    private companion object {
+        const val ElementSize = 3
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldCoreModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldCoreModifier.kt
index 40afbee9..dc92bfc 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldCoreModifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldCoreModifier.kt
@@ -26,7 +26,6 @@
 import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.text.selection.LocalTextSelectionColors
 import androidx.compose.foundation.text2.BasicTextField2
-import androidx.compose.foundation.text2.input.TextFieldState
 import androidx.compose.foundation.text2.input.internal.selection.TextFieldSelectionState
 import androidx.compose.foundation.text2.input.internal.selection.textFieldMagnifierNode
 import androidx.compose.runtime.snapshotFlow
@@ -78,11 +77,10 @@
  *
  * This modifier mostly handles layout and draw.
  */
-@OptIn(ExperimentalFoundationApi::class)
 internal data class TextFieldCoreModifier(
     private val isFocused: Boolean,
     private val textLayoutState: TextLayoutState,
-    private val textFieldState: TextFieldState,
+    private val textFieldState: TransformedTextFieldState,
     private val textFieldSelectionState: TextFieldSelectionState,
     private val cursorBrush: Brush,
     private val writeable: Boolean,
@@ -124,7 +122,7 @@
 internal class TextFieldCoreModifierNode(
     private var isFocused: Boolean,
     private var textLayoutState: TextLayoutState,
-    private var textFieldState: TextFieldState,
+    private var textFieldState: TransformedTextFieldState,
     private var textFieldSelectionState: TextFieldSelectionState,
     private var cursorBrush: Brush,
     private var writeable: Boolean,
@@ -177,7 +175,7 @@
     fun updateNode(
         isFocused: Boolean,
         textLayoutState: TextLayoutState,
-        textFieldState: TextFieldState,
+        textFieldState: TransformedTextFieldState,
         textFieldSelectionState: TextFieldSelectionState,
         cursorBrush: Brush,
         writeable: Boolean,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDecoratorModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDecoratorModifier.kt
index e4ef563..3faa87b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDecoratorModifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldDecoratorModifier.kt
@@ -22,8 +22,6 @@
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.foundation.text2.BasicTextField2
 import androidx.compose.foundation.text2.input.InputTransformation
-import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.deselect
 import androidx.compose.foundation.text2.input.internal.selection.TextFieldSelectionState
 import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.FocusEventModifierNode
@@ -66,6 +64,7 @@
 import androidx.compose.ui.semantics.setText
 import androidx.compose.ui.semantics.textSelectionRange
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.ImeOptions
 import androidx.compose.ui.text.input.KeyboardCapitalization
@@ -84,7 +83,7 @@
  */
 @OptIn(ExperimentalFoundationApi::class)
 internal data class TextFieldDecoratorModifier(
-    private val textFieldState: TextFieldState,
+    private val textFieldState: TransformedTextFieldState,
     private val textLayoutState: TextLayoutState,
     private val textFieldSelectionState: TextFieldSelectionState,
     private val filter: InputTransformation?,
@@ -128,7 +127,7 @@
 /** Modifier node for [TextFieldDecoratorModifier]. */
 @OptIn(ExperimentalFoundationApi::class)
 internal class TextFieldDecoratorModifierNode(
-    var textFieldState: TextFieldState,
+    var textFieldState: TransformedTextFieldState,
     var textLayoutState: TextLayoutState,
     var textFieldSelectionState: TextFieldSelectionState,
     var filter: InputTransformation?,
@@ -169,9 +168,7 @@
      * Manages key events. These events often are sourced by a hardware keyboard but it's also
      * possible that IME or some other platform system simulates a KeyEvent.
      */
-    private val textFieldKeyEventHandler = createTextFieldKeyEventHandler().also {
-        it.setFilter(filter)
-    }
+    private val textFieldKeyEventHandler = createTextFieldKeyEventHandler()
 
     private val keyboardActionScope = object : KeyboardActionScope {
         private val focusManager: FocusManager
@@ -219,7 +216,7 @@
      * Updates all the related properties and invalidates internal state based on the changes.
      */
     fun updateNode(
-        textFieldState: TextFieldState,
+        textFieldState: TransformedTextFieldState,
         textLayoutState: TextLayoutState,
         textFieldSelectionState: TextFieldSelectionState,
         filter: InputTransformation?,
@@ -270,8 +267,6 @@
             invalidateSemantics()
         }
 
-        textFieldKeyEventHandler.setFilter(filter)
-
         if (textFieldSelectionState != previousTextFieldSelectionState) {
             pointerInputNode.resetPointerInputHandler()
         }
@@ -282,54 +277,57 @@
 
     // This function is called inside a snapshot observer.
     override fun SemanticsPropertyReceiver.applySemantics() {
-        val text = textFieldState.text
+        val text = textFieldState.untransformedText
         val selection = text.selectionInChars
+        editableText = AnnotatedString(text.toString())
+        textSelectionRange = selection
 
         getTextLayoutResult {
             textLayoutState.layoutResult?.let { result -> it.add(result) } ?: false
         }
-        editableText = AnnotatedString(text.toString())
-        textSelectionRange = selection
         if (!enabled) disabled()
 
         setText { newText ->
             if (readOnly || !enabled) return@setText false
 
-            textFieldState.editAsUser(filter) {
-                deleteAll()
-                commitText(newText.toString(), 1)
-            }
+            textFieldState.replaceAll(newText)
             true
         }
-        setSelection { start, end, _ ->
-            // BasicTextField2 doesn't have VisualTransformation for the time being and
-            // probably won't have something that uses offsetMapping design. We can safely
-            // skip relativeToOriginalText flag. Assume it's always true.
-
-            if (!enabled) {
-                false
-            } else if (start == selection.start && end == selection.end) {
-                false
-            } else if (start.coerceAtMost(end) >= 0 &&
-                start.coerceAtLeast(end) <= text.length
-            ) {
-                textFieldState.editAsUser(filter) {
-                    setSelection(start, end)
-                }
-                true
+        @Suppress("NAME_SHADOWING")
+        setSelection { start, end, relativeToOriginal ->
+            val text = if (relativeToOriginal) {
+                textFieldState.untransformedText
             } else {
-                false
+                textFieldState.text
             }
+            val selection = text.selectionInChars
+
+            if (!enabled ||
+                minOf(start, end) < 0 ||
+                maxOf(start, end) > text.length
+            ) {
+                return@setSelection false
+            }
+
+            // Selection is already selected, don't need to do any work.
+            if (start == selection.start && end == selection.end) {
+                return@setSelection true
+            }
+
+            val selectionRange = TextRange(start, end)
+            if (relativeToOriginal) {
+                textFieldState.selectUntransformedCharsIn(selectionRange)
+            } else {
+                textFieldState.selectCharsIn(selectionRange)
+            }
+            return@setSelection true
         }
         insertTextAtCursor { newText ->
             if (readOnly || !enabled) return@insertTextAtCursor false
 
-            textFieldState.editAsUser(filter) {
-                // Finish composing text first because when the field is focused the IME
-                // might set composition.
-                commitComposition()
-                commitText(newText.toString(), 1)
-            }
+            // Finish composing text first because when the field is focused the IME
+            // might set composition.
+            textFieldState.replaceSelectedText(newText, clearComposition = true)
             true
         }
         onImeAction(keyboardOptions.imeAction) {
@@ -381,7 +379,7 @@
             }
         } else {
             disposeInputSession()
-            textFieldState.deselect()
+            textFieldState.collapseSelectionToMax()
         }
     }
 
@@ -419,7 +417,6 @@
         return textFieldKeyEventHandler.onKeyEvent(
             event = event,
             textFieldState = textFieldState,
-            inputTransformation = filter,
             textLayoutState = textLayoutState,
             textFieldSelectionState = textFieldSelectionState,
             editable = enabled && !readOnly,
@@ -441,7 +438,6 @@
                 platformSpecificTextInputSession(
                     textFieldState,
                     keyboardOptions.toImeOptions(singleLine),
-                    filter = filter,
                     onImeAction = onImeActionPerformed
                 )
             }
@@ -461,11 +457,9 @@
 /**
  * Runs platform-specific text input logic.
  */
-@OptIn(ExperimentalFoundationApi::class)
 internal expect suspend fun PlatformTextInputSession.platformSpecificTextInputSession(
-    state: TextFieldState,
+    state: TransformedTextFieldState,
     imeOptions: ImeOptions,
-    filter: InputTransformation?,
     onImeAction: ((ImeAction) -> Unit)?
 ): Nothing
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.kt
index 3c886d6..a82ea45 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldKeyEventHandler.kt
@@ -24,8 +24,6 @@
 import androidx.compose.foundation.text.isTypedEvent
 import androidx.compose.foundation.text.platformDefaultKeyMapping
 import androidx.compose.foundation.text.showCharacterPalette
-import androidx.compose.foundation.text2.input.InputTransformation
-import androidx.compose.foundation.text2.input.TextFieldState
 import androidx.compose.foundation.text2.input.internal.TextFieldPreparedSelection.Companion.NoCharacterFound
 import androidx.compose.foundation.text2.input.internal.selection.TextFieldSelectionState
 import androidx.compose.ui.focus.FocusManager
@@ -51,15 +49,10 @@
     private val preparedSelectionState = TextFieldPreparedSelectionState()
     private val deadKeyCombiner = DeadKeyCombiner()
     private val keyMapping = platformDefaultKeyMapping
-    private var filter: InputTransformation? = null
-
-    fun setFilter(filter: InputTransformation?) {
-        this.filter = filter
-    }
 
     open fun onPreKeyEvent(
         event: KeyEvent,
-        textFieldState: TextFieldState,
+        textFieldState: TransformedTextFieldState,
         textFieldSelectionState: TextFieldSelectionState,
         focusManager: FocusManager,
         keyboardController: SoftwareKeyboardController
@@ -75,8 +68,7 @@
 
     open fun onKeyEvent(
         event: KeyEvent,
-        textFieldState: TextFieldState,
-        inputTransformation: InputTransformation?,
+        textFieldState: TransformedTextFieldState,
         textLayoutState: TextLayoutState,
         textFieldSelectionState: TextFieldSelectionState,
         editable: Boolean,
@@ -92,7 +84,7 @@
             if (codePoint != null) {
                 val text = StringBuilder(2).appendCodePointX(codePoint).toString()
                 return if (editable) {
-                    textFieldState.editAsUser(filter) {
+                    textFieldState.editUntransformedTextAsUser {
                         commitComposition()
                         commitText(text, 1)
                     }
@@ -131,7 +123,7 @@
                 KeyCommand.HOME -> moveCursorToHome()
                 KeyCommand.END -> moveCursorToEnd()
                 KeyCommand.DELETE_PREV_CHAR ->
-                    textFieldState.editAsUser(filter) {
+                    textFieldState.editUntransformedTextAsUser {
                         if (!deleteIfSelected()) {
                             deleteSurroundingText(
                                 selection.end - getPrecedingCharacterIndex(),
@@ -142,7 +134,7 @@
                 KeyCommand.DELETE_NEXT_CHAR -> {
                     // Note that some software keyboards, such as Samsung, go through this code
                     // path instead of making calls on the InputConnection directly.
-                    textFieldState.editAsUser(filter) {
+                    textFieldState.editUntransformedTextAsUser {
                         if (!deleteIfSelected()) {
                             val nextCharacterIndex = getNextCharacterIndex()
                             // If there's no next character, it means the cursor is at the end of the
@@ -155,7 +147,7 @@
                 }
 
                 KeyCommand.DELETE_PREV_WORD ->
-                    textFieldState.editAsUser(filter) {
+                    textFieldState.editUntransformedTextAsUser {
                         if (!deleteIfSelected()) {
                             getPreviousWordOffset()?.let {
                                 deleteSurroundingText(selection.end - it, 0)
@@ -163,7 +155,7 @@
                         }
                     }
                 KeyCommand.DELETE_NEXT_WORD ->
-                    textFieldState.editAsUser(filter) {
+                    textFieldState.editUntransformedTextAsUser {
                         if (!deleteIfSelected()) {
                             getNextWordOffset()?.let {
                                 deleteSurroundingText(0, it - selection.end)
@@ -171,7 +163,7 @@
                         }
                     }
                 KeyCommand.DELETE_FROM_LINE_START ->
-                    textFieldState.editAsUser(filter) {
+                    textFieldState.editUntransformedTextAsUser {
                         if (!deleteIfSelected()) {
                             getLineStartByOffset()?.let {
                                 deleteSurroundingText(selection.end - it, 0)
@@ -179,7 +171,7 @@
                         }
                     }
                 KeyCommand.DELETE_TO_LINE_END ->
-                    textFieldState.editAsUser(filter) {
+                    textFieldState.editUntransformedTextAsUser {
                         if (!deleteIfSelected()) {
                             getLineEndByOffset()?.let {
                                 deleteSurroundingText(0, it - selection.end)
@@ -188,7 +180,7 @@
                     }
                 KeyCommand.NEW_LINE ->
                     if (!singleLine) {
-                        textFieldState.editAsUser(filter) {
+                        textFieldState.editUntransformedTextAsUser {
                             commitComposition()
                             commitText("\n", 1)
                         }
@@ -198,7 +190,7 @@
 
                 KeyCommand.TAB ->
                     if (!singleLine) {
-                        textFieldState.editAsUser(filter) {
+                        textFieldState.editUntransformedTextAsUser {
                             commitComposition()
                             commitText("\t", 1)
                         }
@@ -243,7 +235,7 @@
     }
 
     private inline fun preparedSelectionContext(
-        state: TextFieldState,
+        state: TransformedTextFieldState,
         textLayoutState: TextLayoutState,
         block: TextFieldPreparedSelection.() -> Unit
     ) {
@@ -255,9 +247,7 @@
         preparedSelection.block()
         if (preparedSelection.selection != preparedSelection.initialValue.selectionInChars) {
             // selection changes are applied atomically at the end of context evaluation
-            state.editAsUser(filter) {
-                setSelection(preparedSelection.selection.start, preparedSelection.selection.end)
-            }
+            state.selectCharsIn(preparedSelection.selection)
         }
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldLayoutStateCache.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldLayoutStateCache.kt
index 36f9333..794c6ab 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldLayoutStateCache.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldLayoutStateCache.kt
@@ -19,12 +19,9 @@
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.text.InternalFoundationTextApi
 import androidx.compose.foundation.text.TextDelegate
-import androidx.compose.foundation.text2.input.CodepointTransformation
-import androidx.compose.foundation.text2.input.SingleLineCodepointTransformation
 import androidx.compose.foundation.text2.input.TextFieldState
 import androidx.compose.foundation.text2.input.internal.TextFieldLayoutStateCache.MeasureInputs
 import androidx.compose.foundation.text2.input.internal.TextFieldLayoutStateCache.NonMeasureInputs
-import androidx.compose.foundation.text2.input.toVisualText
 import androidx.compose.runtime.SnapshotMutationPolicy
 import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
@@ -103,15 +100,13 @@
      * @see layoutWithNewMeasureInputs
      */
     fun updateNonMeasureInputs(
-        textFieldState: TextFieldState,
-        codepointTransformation: CodepointTransformation?,
+        textFieldState: TransformedTextFieldState,
         textStyle: TextStyle,
         singleLine: Boolean,
         softWrap: Boolean,
     ) {
         nonMeasureInputs = NonMeasureInputs(
             textFieldState = textFieldState,
-            codepointTransformation = codepointTransformation,
             textStyle = textStyle,
             singleLine = singleLine,
             softWrap = softWrap,
@@ -150,16 +145,7 @@
         nonMeasureInputs: NonMeasureInputs,
         measureInputs: MeasureInputs
     ): TextLayoutResult {
-        // If there's a transformation, we need to apply it every time, since it may contain state
-        // reads and conditional logic that we can't check avoid running.
-        // First prefer provided codepointTransformation if not null, e.g. BasicSecureTextField
-        // would send PasswordTransformation. Second, apply a SingleLineCodepointTransformation if
-        // text field is configured to be single line. Else, don't apply any visual transformation.
-        val appliedCodepointTransformation = nonMeasureInputs.codepointTransformation
-            ?: SingleLineCodepointTransformation.takeIf { nonMeasureInputs.singleLine }
-        // This will return untransformedText if the transformation is null.
         val visualText = nonMeasureInputs.textFieldState.text
-            .toVisualText(appliedCodepointTransformation)
 
         // Use withCurrent here so the cache itself is never reported as a read state object. It
         // doesn't need to be, because it's always guaranteed to return the same value for the same
@@ -324,8 +310,7 @@
 
     // region Input holders
     private class NonMeasureInputs(
-        val textFieldState: TextFieldState,
-        val codepointTransformation: CodepointTransformation?,
+        val textFieldState: TransformedTextFieldState,
         val textStyle: TextStyle,
         val singleLine: Boolean,
         val softWrap: Boolean,
@@ -333,7 +318,6 @@
 
         override fun toString(): String = "NonMeasureInputs(" +
             "textFieldState=$textFieldState, " +
-            "codepointTransformation=$codepointTransformation, " +
             "textStyle=$textStyle, " +
             "singleLine=$singleLine, " +
             "softWrap=$softWrap" +
@@ -355,7 +339,6 @@
                         // invalidating if the TextFieldState is a different instance but with the same
                         // text, but that is unlikely to happen.
                         a.textFieldState === b.textFieldState &&
-                            a.codepointTransformation == b.codepointTransformation &&
                             a.textStyle.hasSameLayoutAffectingAttributes(b.textStyle) &&
                             a.singleLine == b.singleLine &&
                             a.softWrap == b.softWrap
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldTextLayoutModifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldTextLayoutModifier.kt
index ec07b7e..303888a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldTextLayoutModifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldTextLayoutModifier.kt
@@ -16,9 +16,6 @@
 
 package androidx.compose.foundation.text2.input.internal
 
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.CodepointTransformation
-import androidx.compose.foundation.text2.input.TextFieldState
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.FirstBaseline
 import androidx.compose.ui.layout.LastBaseline
@@ -46,11 +43,9 @@
  * coordinates of [TextLayoutResult] to make it relatively easier to calculate the offset between
  * exact touch coordinates and where they map on the [TextLayoutResult].
  */
-@OptIn(ExperimentalFoundationApi::class)
 internal data class TextFieldTextLayoutModifier(
     private val textLayoutState: TextLayoutState,
-    private val textFieldState: TextFieldState,
-    private val codepointTransformation: CodepointTransformation?,
+    private val textFieldState: TransformedTextFieldState,
     private val textStyle: TextStyle,
     private val singleLine: Boolean,
     private val onTextLayout: Density.(getResult: () -> TextLayoutResult?) -> Unit
@@ -58,7 +53,6 @@
     override fun create(): TextFieldTextLayoutModifierNode = TextFieldTextLayoutModifierNode(
         textLayoutState = textLayoutState,
         textFieldState = textFieldState,
-        codepointTransformation = codepointTransformation,
         textStyle = textStyle,
         singleLine = singleLine,
         onTextLayout = onTextLayout
@@ -68,7 +62,6 @@
         node.updateNode(
             textLayoutState = textLayoutState,
             textFieldState = textFieldState,
-            codepointTransformation = codepointTransformation,
             textStyle = textStyle,
             singleLine = singleLine,
             onTextLayout = onTextLayout
@@ -80,11 +73,9 @@
     }
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 internal class TextFieldTextLayoutModifierNode(
     private var textLayoutState: TextLayoutState,
-    textFieldState: TextFieldState,
-    codepointTransformation: CodepointTransformation?,
+    textFieldState: TransformedTextFieldState,
     textStyle: TextStyle,
     singleLine: Boolean,
     onTextLayout: Density.(getResult: () -> TextLayoutResult?) -> Unit
@@ -97,7 +88,6 @@
         textLayoutState.onTextLayout = onTextLayout
         textLayoutState.updateNonMeasureInputs(
             textFieldState = textFieldState,
-            codepointTransformation = codepointTransformation,
             textStyle = textStyle,
             singleLine = singleLine,
             softWrap = !singleLine
@@ -109,8 +99,7 @@
      */
     fun updateNode(
         textLayoutState: TextLayoutState,
-        textFieldState: TextFieldState,
-        codepointTransformation: CodepointTransformation?,
+        textFieldState: TransformedTextFieldState,
         textStyle: TextStyle,
         singleLine: Boolean,
         onTextLayout: Density.(getResult: () -> TextLayoutResult?) -> Unit
@@ -119,7 +108,6 @@
         this.textLayoutState.onTextLayout = onTextLayout
         this.textLayoutState.updateNonMeasureInputs(
             textFieldState = textFieldState,
-            codepointTransformation = codepointTransformation,
             textStyle = textStyle,
             singleLine = singleLine,
             softWrap = !singleLine
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextLayoutState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextLayoutState.kt
index 991dae0..883e4cf 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextLayoutState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextLayoutState.kt
@@ -17,8 +17,6 @@
 package androidx.compose.foundation.text2.input.internal
 
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.CodepointTransformation
-import androidx.compose.foundation.text2.input.TextFieldState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.neverEqualPolicy
@@ -65,15 +63,13 @@
      * @see layoutWithNewMeasureInputs
      */
     fun updateNonMeasureInputs(
-        textFieldState: TextFieldState,
-        codepointTransformation: CodepointTransformation?,
+        textFieldState: TransformedTextFieldState,
         textStyle: TextStyle,
         singleLine: Boolean,
         softWrap: Boolean,
     ) {
         layoutCache.updateNonMeasureInputs(
             textFieldState = textFieldState,
-            codepointTransformation = codepointTransformation,
             textStyle = textStyle,
             singleLine = singleLine,
             softWrap = softWrap,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextPreparedSelection.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextPreparedSelection.kt
index 63681d0..5baac993 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextPreparedSelection.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TextPreparedSelection.kt
@@ -21,7 +21,6 @@
 import androidx.compose.foundation.text.findParagraphEnd
 import androidx.compose.foundation.text.findParagraphStart
 import androidx.compose.foundation.text.findPrecedingBreak
-import androidx.compose.foundation.text2.input.TextFieldState
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.text.TextLayoutResult
@@ -66,7 +65,7 @@
  */
 @OptIn(ExperimentalFoundationApi::class)
 internal class TextFieldPreparedSelection(
-    private val state: TextFieldState,
+    private val state: TransformedTextFieldState,
     private val textLayoutState: TextLayoutState,
     private val textPreparedSelectionState: TextFieldPreparedSelectionState
 ) {
@@ -147,7 +146,7 @@
      *
      * @param resetCachedX Whether to reset the cachedX parameter in [TextFieldPreparedSelectionState].
      */
-    inline fun applyIfNotEmpty(
+    private inline fun applyIfNotEmpty(
         resetCachedX: Boolean = true,
         block: TextFieldPreparedSelection.() -> Unit
     ): TextFieldPreparedSelection {
@@ -330,8 +329,8 @@
         }
     }
 
-    // it selects a text from the original selection start to a current selection end
-    fun selectMovement() = applyIfNotEmpty(false) {
+    /** Selects a text from the original selection start to a current selection end. */
+    fun selectMovement() = applyIfNotEmpty(resetCachedX = false) {
         selection = TextRange(initialValue.selectionInChars.start, selection.end)
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TransformedTextFieldState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TransformedTextFieldState.kt
new file mode 100644
index 0000000..bf2e53e
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/TransformedTextFieldState.kt
@@ -0,0 +1,358 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text2.input.internal
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text2.input.CodepointTransformation
+import androidx.compose.foundation.text2.input.InputTransformation
+import androidx.compose.foundation.text2.input.TextFieldCharSequence
+import androidx.compose.foundation.text2.input.TextFieldState
+import androidx.compose.foundation.text2.input.toVisualText
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.ui.text.TextRange
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+/**
+ * A mutable view of a [TextFieldState] where the text and selection values are transformed by a
+ * [CodepointTransformation].
+ *
+ * [text] returns the transformed text, with selection and composition mapped to the corresponding
+ * offsets from the untransformed text. The transformed text is cached in a
+ * [derived state][derivedStateOf] and only recalculated when the [TextFieldState] changes or some
+ * state read by the [CodepointTransformation] changes.
+ *
+ * This class defines methods for various operations that can be performed on the underlying
+ * [TextFieldState]. When possible, these methods should be used instead of editing the state
+ * directly, since this class ensures the correct offset mappings are used. If an operation is too
+ * complex to warrant a method here, use [editUntransformedTextAsUser] but be careful to make sure
+ * any offsets are mapped correctly.
+ *
+ * To map offsets from transformed to untransformed text or back, use the [mapFromTransformed] and
+ * [mapToTransformed] methods.
+ *
+ * All operations call [TextFieldState.editAsUser] internally and pass [inputTransformation].
+ */
+@OptIn(ExperimentalFoundationApi::class)
+@Stable
+internal class TransformedTextFieldState(
+    private val textFieldState: TextFieldState,
+    private val inputTransformation: InputTransformation?,
+    private val codepointTransformation: CodepointTransformation?,
+) {
+    private val transformedText: State<TransformedText?>? =
+        // Don't allocate a derived state object if we don't need it, they're expensive.
+        codepointTransformation?.let { transformation ->
+            derivedStateOf {
+                // text is a state read. transformation may also perform state reads when ran.
+                calculateTransformedText(textFieldState.text, transformation)
+            }
+        }
+
+    /**
+     * The text that should be presented to the user in most cases. Ifa  [CodepointTransformation]
+     * is specified, this text has the transformation applied. If there's no transformation,
+     * this will be the same as [untransformedText].
+     */
+    val text: TextFieldCharSequence
+        get() = transformedText?.value?.text ?: textFieldState.text
+
+    /**
+     * The raw text in the underlying [TextFieldState]. This text does not have any
+     * [CodepointTransformation] applied.
+     */
+    val untransformedText: TextFieldCharSequence
+        get() = textFieldState.text
+
+    fun placeCursorBeforeCharAt(transformedOffset: Int) {
+        selectCharsIn(TextRange(transformedOffset))
+    }
+
+    fun selectCharsIn(transformedRange: TextRange) {
+        val untransformedRange = mapFromTransformed(transformedRange)
+        selectUntransformedCharsIn(untransformedRange)
+    }
+
+    fun selectUntransformedCharsIn(untransformedRange: TextRange) {
+        textFieldState.editAsUser(inputTransformation) {
+            setSelection(untransformedRange.start, untransformedRange.end)
+        }
+    }
+
+    fun replaceAll(newText: CharSequence) {
+        textFieldState.editAsUser(inputTransformation) {
+            deleteAll()
+            commitText(newText.toString(), 1)
+        }
+    }
+
+    fun selectAll() {
+        textFieldState.editAsUser(inputTransformation) {
+            setSelection(0, length)
+        }
+    }
+
+    fun deleteSelectedText() {
+        textFieldState.editAsUser(inputTransformation) {
+            // `selection` is read from the buffer, so we don't need to transform it.
+            delete(selection.min, selection.max)
+            setSelection(selection.min, selection.min)
+        }
+    }
+
+    fun replaceSelectedText(
+        newText: CharSequence,
+        clearComposition: Boolean = false
+    ) {
+        textFieldState.editAsUser(inputTransformation) {
+            if (clearComposition) {
+                commitComposition()
+            }
+
+            // `selection` is read from the buffer, so we don't need to transform it.
+            val selection = selection
+            replace(
+                selection.min,
+                selection.max,
+                newText
+            )
+            val cursor = selection.min + newText.length
+            setSelection(cursor, cursor)
+        }
+    }
+
+    fun collapseSelectionToMax() {
+        textFieldState.editAsUser(inputTransformation) {
+            // `selection` is read from the buffer, so we don't need to transform it.
+            setSelection(selection.max, selection.max)
+        }
+    }
+
+    fun collapseSelectionToEnd() {
+        textFieldState.editAsUser(inputTransformation) {
+            // `selection` is read from the buffer, so we don't need to transform it.
+            setSelection(selection.end, selection.end)
+        }
+    }
+
+    /**
+     * Runs [block] with a buffer that contains the source untransformed text. This is the text that
+     * will be fed into the [codepointTransformation]. Any operations performed on this buffer MUST
+     * take care to explicitly convert between transformed and untransformed offsets and ranges.
+     * When possible, use the other methods on this class to manipulate selection to avoid having
+     * to do these conversions manually.
+     *
+     * @see mapToTransformed
+     * @see mapFromTransformed
+     */
+    inline fun editUntransformedTextAsUser(
+        notifyImeOfChanges: Boolean = true,
+        block: EditingBuffer.() -> Unit
+    ) {
+        textFieldState.editAsUser(
+            inputTransformation = inputTransformation,
+            notifyImeOfChanges = notifyImeOfChanges,
+            block = block
+        )
+    }
+
+    /**
+     * Maps an [offset] in the untransformed text to the corresponding offset or range in [text].
+     *
+     * An untransformed offset will map to non-collapsed range if the offset is in the middle of
+     * a surrogate pair in the untransformed text, in which case it will return the range of the
+     * codepoint that the surrogate maps to. Offsets on either side of a surrogate pair will return
+     * collapsed ranges.
+     *
+     * If there is no transformation, or the transformation does not change the text, a collapsed
+     * range of [offset] will be returned.
+     *
+     * @see mapFromTransformed
+     */
+    fun mapToTransformed(offset: Int): TextRange {
+        val mapping = transformedText?.value?.offsetMapping ?: return TextRange(offset)
+        return mapping.mapFromSource(offset)
+    }
+
+    /**
+     * Maps a [range] in the untransformed text to the corresponding range in [text].
+     *
+     * If there is no transformation, or the transformation does not change the text, [range]
+     * will be returned.
+     *
+     * @see mapFromTransformed
+     */
+    fun mapToTransformed(range: TextRange): TextRange {
+        val mapping = transformedText?.value?.offsetMapping ?: return range
+        return mapToTransformed(range, mapping)
+    }
+
+    /**
+     * Maps an [offset] in [text] to the corresponding offset in the untransformed text.
+     *
+     * Multiple transformed offsets may map to the same untransformed offset. In particular, any
+     * offset in the middle of a surrogate pair will map to offset of the corresponding codepoint
+     * in the untransformed text.
+     *
+     * If there is no transformation, or the transformation does not change the text, [offset]
+     * will be returned.
+     *
+     * @see mapToTransformed
+     */
+    fun mapFromTransformed(offset: Int): Int {
+        val mapping = transformedText?.value?.offsetMapping ?: return offset
+        return mapping.mapFromDest(offset).min
+    }
+
+    /**
+     * Maps a [range] in [text] to the corresponding range in the untransformed text.
+     *
+     * If there is no transformation, or the transformation does not change the text, [range]
+     * will be returned.
+     *
+     * @see mapToTransformed
+     */
+    fun mapFromTransformed(range: TextRange): TextRange {
+        val mapping = transformedText?.value?.offsetMapping ?: return range
+        return mapFromTransformed(range, mapping)
+    }
+
+    // TODO(b/296583846) Get rid of this.
+    /**
+     * Adds [notifyImeListener] to the underlying [TextFieldState] and then suspends until
+     * cancelled, removing the listener before continuing.
+     */
+    suspend fun collectImeNotifications(
+        notifyImeListener: TextFieldState.NotifyImeListener
+    ): Nothing {
+        suspendCancellableCoroutine<Nothing> { continuation ->
+            textFieldState.addNotifyImeListener(notifyImeListener)
+            continuation.invokeOnCancellation {
+                textFieldState.removeNotifyImeListener(notifyImeListener)
+            }
+        }
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is TransformedTextFieldState) return false
+        if (textFieldState != other.textFieldState) return false
+        return codepointTransformation == other.codepointTransformation
+    }
+
+    override fun hashCode(): Int {
+        var result = textFieldState.hashCode()
+        result = 31 * result + (codepointTransformation?.hashCode() ?: 0)
+        return result
+    }
+
+    override fun toString(): String = "TransformedTextFieldState(" +
+        "textFieldState=$textFieldState, " +
+        "codepointTransformation=$codepointTransformation, " +
+        "transformedText=$transformedText, " +
+        "text=\"$text\"" +
+        ")"
+
+    private data class TransformedText(
+        val text: TextFieldCharSequence,
+        val offsetMapping: OffsetMappingCalculator,
+    )
+
+    private companion object {
+
+        /**
+         * Applies a [CodepointTransformation] to a [TextFieldCharSequence], returning the
+         * transformed text content, the selection/cursor from the [untransformedText] mapped to the
+         * offsets in the transformed text, and an [OffsetMappingCalculator] that can be used to map
+         * offsets in both directions between the transformed and untransformed text.
+         *
+         * This function is relatively expensive, since it creates a copy of [untransformedText], so
+         * its result should be cached.
+         */
+        @kotlin.jvm.JvmStatic
+        private fun calculateTransformedText(
+            untransformedText: TextFieldCharSequence,
+            codepointTransformation: CodepointTransformation
+        ): TransformedText? {
+            val offsetMappingCalculator = OffsetMappingCalculator()
+
+            // This is the call to external code. Returns same instance if no codepoints change.
+            val transformedText =
+                untransformedText.toVisualText(codepointTransformation, offsetMappingCalculator)
+
+            // Avoid allocations + mapping if there weren't actually any transformations.
+            if (transformedText === untransformedText) {
+                return null
+            }
+
+            val transformedTextWithSelection = TextFieldCharSequence(
+                text = transformedText,
+                // Pass the calculator explicitly since the one on transformedText won't be updated
+                // yet.
+                selection = mapToTransformed(
+                    untransformedText.selectionInChars,
+                    offsetMappingCalculator
+                ),
+                composition = untransformedText.compositionInChars?.let {
+                    mapToTransformed(it, offsetMappingCalculator)
+                }
+            )
+            return TransformedText(transformedTextWithSelection, offsetMappingCalculator)
+        }
+
+        @kotlin.jvm.JvmStatic
+        private fun mapToTransformed(
+            range: TextRange,
+            mapping: OffsetMappingCalculator
+        ): TextRange {
+            val transformedStart = mapping.mapFromSource(range.start)
+            // Avoid calculating mapping again if it's going to be the same value.
+            val transformedEnd = if (range.collapsed) transformedStart else {
+                mapping.mapFromSource(range.end)
+            }
+
+            val transformedMin = minOf(transformedStart.min, transformedEnd.min)
+            val transformedMax = maxOf(transformedStart.max, transformedEnd.max)
+            return if (range.reversed) {
+                TextRange(transformedMax, transformedMin)
+            } else {
+                TextRange(transformedMin, transformedMax)
+            }
+        }
+
+        @kotlin.jvm.JvmStatic
+        private fun mapFromTransformed(
+            range: TextRange,
+            mapping: OffsetMappingCalculator
+        ): TextRange {
+            val untransformedStart = mapping.mapFromDest(range.start)
+            // Avoid calculating mapping again if it's going to be the same value.
+            val untransformedEnd = if (range.collapsed) untransformedStart else {
+                mapping.mapFromDest(range.end)
+            }
+
+            val untransformedMin = minOf(untransformedStart.min, untransformedEnd.min)
+            val untransformedMax = maxOf(untransformedStart.max, untransformedEnd.max)
+            return if (range.reversed) {
+                TextRange(untransformedMax, untransformedMin)
+            } else {
+                TextRange(untransformedMin, untransformedMax)
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldMagnifier.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldMagnifier.kt
index 74309cf..180fe23 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldMagnifier.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldMagnifier.kt
@@ -19,8 +19,8 @@
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.text.Handle
 import androidx.compose.foundation.text.selection.visibleBounds
-import androidx.compose.foundation.text2.input.TextFieldState
 import androidx.compose.foundation.text2.input.internal.TextLayoutState
+import androidx.compose.foundation.text2.input.internal.TransformedTextFieldState
 import androidx.compose.foundation.text2.input.internal.coerceIn
 import androidx.compose.foundation.text2.input.internal.fromInnerToDecoration
 import androidx.compose.ui.geometry.Offset
@@ -35,14 +35,13 @@
 import androidx.compose.ui.unit.IntSize
 import kotlin.math.absoluteValue
 
-@OptIn(ExperimentalFoundationApi::class)
 internal abstract class TextFieldMagnifierNode : DelegatingNode(),
     OnGloballyPositionedModifier,
     DrawModifierNode,
     SemanticsModifierNode {
 
     abstract fun update(
-        textFieldState: TextFieldState,
+        textFieldState: TransformedTextFieldState,
         textFieldSelectionState: TextFieldSelectionState,
         textLayoutState: TextLayoutState,
         isFocused: Boolean
@@ -55,10 +54,9 @@
     override fun SemanticsPropertyReceiver.applySemantics() {}
 }
 
-@OptIn(ExperimentalFoundationApi::class)
 @Suppress("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType")
 internal expect fun textFieldMagnifierNode(
-    textFieldState: TextFieldState,
+    textFieldState: TransformedTextFieldState,
     textFieldSelectionState: TextFieldSelectionState,
     textLayoutState: TextLayoutState,
     isFocused: Boolean
@@ -66,7 +64,7 @@
 
 @OptIn(ExperimentalFoundationApi::class)
 internal fun calculateSelectionMagnifierCenterAndroid(
-    textFieldState: TextFieldState,
+    textFieldState: TransformedTextFieldState,
     selectionState: TextFieldSelectionState,
     textLayoutState: TextLayoutState,
     magnifierSize: IntSize
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionState.kt
index 0db02b3..a6a69b8 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/TextFieldSelectionState.kt
@@ -30,12 +30,10 @@
 import androidx.compose.foundation.text.selection.getTextFieldSelectionLayout
 import androidx.compose.foundation.text.selection.isPrecisePointer
 import androidx.compose.foundation.text.selection.visibleBounds
-import androidx.compose.foundation.text2.input.InputTransformation
 import androidx.compose.foundation.text2.input.TextFieldCharSequence
-import androidx.compose.foundation.text2.input.TextFieldState
 import androidx.compose.foundation.text2.input.getSelectedText
-import androidx.compose.foundation.text2.input.internal.EditingBuffer
 import androidx.compose.foundation.text2.input.internal.TextLayoutState
+import androidx.compose.foundation.text2.input.internal.TransformedTextFieldState
 import androidx.compose.foundation.text2.input.internal.coerceIn
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
@@ -70,12 +68,11 @@
 
 @OptIn(ExperimentalFoundationApi::class)
 internal class TextFieldSelectionState(
-    private val textFieldState: TextFieldState,
+    private val textFieldState: TransformedTextFieldState,
     private val textLayoutState: TextLayoutState,
-    private var inputTransformation: InputTransformation?,
     private var density: Density,
     private var editable: Boolean,
-    var isFocused: Boolean
+    var isFocused: Boolean,
 ) {
     /**
      * [HapticFeedback] handle to perform haptic feedback.
@@ -271,14 +268,12 @@
         hapticFeedBack: HapticFeedback,
         clipboardManager: ClipboardManager,
         textToolbar: TextToolbar,
-        inputTransformation: InputTransformation?,
         density: Density,
         editable: Boolean,
     ) {
         this.hapticFeedBack = hapticFeedBack
         this.clipboardManager = clipboardManager
         this.textToolbar = textToolbar
-        this.inputTransformation = inputTransformation
         this.density = density
         this.editable = editable
     }
@@ -415,9 +410,7 @@
                     val cursorIndex = textLayoutState.getOffsetForPosition(offset)
                     // update the state
                     if (cursorIndex >= 0) {
-                        editAsUser {
-                            selectCharsIn(TextRange(cursorIndex))
-                        }
+                        textFieldState.placeCursorBeforeCharAt(cursorIndex)
                     }
                 }
             },
@@ -441,9 +434,7 @@
                     isStartHandle = false,
                     adjustment = SelectionAdjustment.Word,
                 )
-                editAsUser {
-                    selectCharsIn(newSelection)
-                }
+                textFieldState.selectCharsIn(newSelection)
             }
         )
     }
@@ -487,9 +478,7 @@
                     change.consume()
                     // TODO: only perform haptic feedback if filter does not override the change
                     hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
-                    editAsUser {
-                        selectCharsIn(newSelection)
-                    }
+                    textFieldState.selectCharsIn(newSelection)
                 }
             )
         } finally {
@@ -529,9 +518,7 @@
                     val offset = textLayoutState.getOffsetForPosition(dragStartOffset)
 
                     hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
-                    editAsUser {
-                        selectCharsIn(TextRange(offset))
-                    }
+                    textFieldState.placeCursorBeforeCharAt(offset)
                     showCursorHandle = true
                     showCursorHandleToolbar = true
                 } else {
@@ -549,9 +536,7 @@
                         isStartHandle = false,
                         adjustment = SelectionAdjustment.CharacterWithWordAccelerate,
                     )
-                    editAsUser {
-                        selectCharsIn(newSelection)
-                    }
+                    textFieldState.selectCharsIn(newSelection)
                     showCursorHandle = false
                     // For touch, set the begin offset to the adjusted selection.
                     // When char based selection is used, we want to ensure we snap the
@@ -645,9 +630,7 @@
                 // Do not allow selection to collapse on itself while dragging. Selection can
                 // reverse but does not collapse.
                 if (prevSelection.collapsed || !newSelection.collapsed) {
-                    editAsUser {
-                        selectCharsIn(newSelection)
-                    }
+                    textFieldState.selectCharsIn(newSelection)
                 }
                 updateHandleDragging(
                     handle = actingHandle,
@@ -719,9 +702,7 @@
                     // Do not allow selection to collapse on itself while dragging selection
                     // handles. Selection can reverse but does not collapse.
                     if (prevSelection.collapsed || !newSelection.collapsed) {
-                        editAsUser {
-                            selectCharsIn(newSelection)
-                        }
+                        textFieldState.selectCharsIn(newSelection)
                     }
                 }
             )
@@ -951,10 +932,7 @@
 
         clipboardManager?.setText(AnnotatedString(text.getSelectedText().toString()))
 
-        editAsUser {
-            replace(selection.min, selection.max, "")
-            selectCharsIn(TextRange(selection.min))
-        }
+        textFieldState.deleteSelectedText()
         // TODO(halilibo): undoManager force snapshot
     }
 
@@ -976,9 +954,7 @@
 
         if (!cancelSelection) return
 
-        editAsUser {
-            selectCharsIn(TextRange(selection.max))
-        }
+        textFieldState.collapseSelectionToMax()
     }
 
     /**
@@ -993,15 +969,7 @@
     fun paste() {
         val clipboardText = clipboardManager?.getText()?.text ?: return
 
-        editAsUser {
-            val selection = textFieldState.text.selectionInChars
-            replace(
-                selection.min,
-                selection.max,
-                clipboardText
-            )
-            selectCharsIn(TextRange(selection.min + clipboardText.length))
-        }
+        textFieldState.replaceSelectedText(clipboardText)
         // TODO(halilibo): undoManager force snapshot
     }
 
@@ -1038,7 +1006,7 @@
 
         val selectAll: (() -> Unit)? = if (selection.length != textFieldState.text.length) {
             {
-                editAsUser { selectCharsIn(TextRange(0, length)) }
+                textFieldState.selectAll()
                 showCursorHandleToolbar = false
             }
         } else null
@@ -1053,24 +1021,14 @@
     }
 
     fun deselect() {
-        val selection = textFieldState.text.selectionInChars
-        if (!selection.collapsed) {
-            editAsUser {
-                selectCharsIn(TextRange(selection.end))
-            }
+        if (!textFieldState.text.selectionInChars.collapsed) {
+            textFieldState.collapseSelectionToEnd()
         }
 
         showCursorHandle = false
         showCursorHandleToolbar = false
     }
 
-    /**
-     * Edits the TextFieldState content with a filter applied if available.
-     */
-    private fun editAsUser(block: EditingBuffer.() -> Unit) {
-        textFieldState.editAsUser(inputTransformation, block = block)
-    }
-
     private fun hideTextToolbar() {
         if (textToolbar?.status == TextToolbarStatus.Shown) {
             textToolbar?.hide()
@@ -1162,10 +1120,6 @@
 
 private fun TextRange.reverse() = TextRange(end, start)
 
-private fun EditingBuffer.selectCharsIn(range: TextRange) {
-    setSelection(range.start, range.end)
-}
-
 private const val DEBUG = false
 private const val DEBUG_TAG = "TextFieldSelectionState"
 
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/DesktopTextInputSession.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/DesktopTextInputSession.desktop.kt
index 9b902c0..e71d352 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/DesktopTextInputSession.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/DesktopTextInputSession.desktop.kt
@@ -19,8 +19,6 @@
 package androidx.compose.foundation.text2.input.internal
 
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.InputTransformation
-import androidx.compose.foundation.text2.input.TextFieldState
 import androidx.compose.ui.platform.PlatformTextInputSession
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.ImeOptions
@@ -30,9 +28,8 @@
  * Runs desktop-specific text input session logic.
  */
 internal actual suspend fun PlatformTextInputSession.platformSpecificTextInputSession(
-    state: TextFieldState,
+    state: TransformedTextFieldState,
     imeOptions: ImeOptions,
-    filter: InputTransformation?,
     onImeAction: ((ImeAction) -> Unit)?
 ): Nothing {
     // TODO(b/267235947) Wire up desktop.
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/DesktopTextFieldMagnifier.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/DesktopTextFieldMagnifier.kt
index 52b60da..948806a 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/DesktopTextFieldMagnifier.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text2/input/internal/selection/DesktopTextFieldMagnifier.kt
@@ -16,22 +16,20 @@
 
 package androidx.compose.foundation.text2.input.internal.selection
 
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.text2.input.TextFieldState
 import androidx.compose.foundation.text2.input.internal.TextLayoutState
+import androidx.compose.foundation.text2.input.internal.TransformedTextFieldState
 
 /**
  * There is no magnifier on Desktop. Return a noop [TextFieldMagnifierNode] implementation.
  */
-@OptIn(ExperimentalFoundationApi::class)
 internal actual fun textFieldMagnifierNode(
-    textFieldState: TextFieldState,
+    textFieldState: TransformedTextFieldState,
     textFieldSelectionState: TextFieldSelectionState,
     textLayoutState: TextLayoutState,
     isFocused: Boolean
 ) = object : TextFieldMagnifierNode() {
     override fun update(
-        textFieldState: TextFieldState,
+        textFieldState: TransformedTextFieldState,
         textFieldSelectionState: TextFieldSelectionState,
         textLayoutState: TextLayoutState,
         isFocused: Boolean
diff --git a/compose/foundation/foundation/src/jvmMain/kotlin/androidx/compose/foundation/text2/input/internal/CodepointHelpers.jvm.kt b/compose/foundation/foundation/src/jvmMain/kotlin/androidx/compose/foundation/text2/input/internal/CodepointHelpers.jvm.kt
new file mode 100644
index 0000000..95a0458
--- /dev/null
+++ b/compose/foundation/foundation/src/jvmMain/kotlin/androidx/compose/foundation/text2/input/internal/CodepointHelpers.jvm.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text2.input.internal
+
+internal actual fun CharSequence.codePointAt(index: Int): Int =
+    java.lang.Character.codePointAt(this, index)
+
+internal actual fun CharSequence.codePointCount(): Int =
+    java.lang.Character.codePointCount(this, 0, length)
+
+internal actual fun charCount(codePoint: Int): Int =
+    java.lang.Character.charCount(codePoint)
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionFakes.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionFakes.kt
index 37377dc..624fb70 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionFakes.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionFakes.kt
@@ -268,9 +268,11 @@
     var textToReturn: AnnotatedString? = null
 
     var rawStartHandleOffset = 0
-    var startHandleDirection = Direction.ON
+    var startXHandleDirection = Direction.ON
+    var startYHandleDirection = Direction.ON
     var rawEndHandleOffset = 0
-    var endHandleDirection = Direction.ON
+    var endXHandleDirection = Direction.ON
+    var endYHandleDirection = Direction.ON
     var rawPreviousHandleOffset = -1 // -1 = no previous offset
 
     private val selectableKey = 1L
@@ -291,9 +293,11 @@
         builder.appendInfo(
             selectableKey,
             rawStartHandleOffset,
-            startHandleDirection,
+            startXHandleDirection,
+            startYHandleDirection,
             rawEndHandleOffset,
-            endHandleDirection,
+            endXHandleDirection,
+            endYHandleDirection,
             rawPreviousHandleOffset,
             getTextLayoutResultMock(),
         )
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionLayoutStartSlot2DTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionLayoutStartSlot2DTest.kt
new file mode 100644
index 0000000..619627d
--- /dev/null
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionLayoutStartSlot2DTest.kt
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text.selection
+
+import androidx.compose.foundation.text.selection.Direction.AFTER
+import androidx.compose.foundation.text.selection.Direction.BEFORE
+import androidx.compose.foundation.text.selection.Direction.ON
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.text.TextRange
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+/**
+ * The visual representation of the 2D tests looks like a box with for texts laid out like this:
+ * ```
+ *           LEFT  MIDDLE_LEFT MIDDLE MIDDLE_RIGHT  RIGHT
+ *            |         |         |        |          |
+ *  TOP           ┌───────────────────────────────┐
+ *                │ ┌────────┐         ┌────────┐ │
+ *  MIDDLE_TOP    │ │ text 1 │         │ text 2 │ │
+ *                │ └────────┘         └────────┘ │
+ *  MIDDLE        │                               │
+ *                │ ┌────────┐         ┌────────┐ │
+ *  MIDDLE_BOTTOM │ │ text 3 │         │ text 4 │ │
+ *                │ └────────┘         └────────┘ │
+ *  BOTTOM        └───────────────────────────────┘
+ * ```
+ * The labels on the x and y axis that will be referenced in the below tests.
+ */
+open class SelectionLayout2DTest {
+    enum class TestHorizontal {
+        LEFT, MIDDLE_LEFT, MIDDLE, MIDDLE_RIGHT, RIGHT
+    }
+
+    enum class TestVertical {
+        TOP,
+        MIDDLE_TOP,
+        MIDDLE,
+        MIDDLE_BOTTOM,
+        BOTTOM
+    }
+
+    internal fun getDirectionsForX(horizontal: TestHorizontal): List<Direction> =
+        when (horizontal) {
+            TestHorizontal.LEFT -> listOf(BEFORE, BEFORE, BEFORE, BEFORE)
+            TestHorizontal.MIDDLE_LEFT -> listOf(ON, BEFORE, ON, BEFORE)
+            TestHorizontal.MIDDLE -> listOf(AFTER, BEFORE, AFTER, BEFORE)
+            TestHorizontal.MIDDLE_RIGHT -> listOf(AFTER, ON, AFTER, ON)
+            TestHorizontal.RIGHT -> listOf(AFTER, AFTER, AFTER, AFTER)
+        }
+
+    internal fun getDirectionsForY(vertical: TestVertical): List<Direction> =
+        when (vertical) {
+            TestVertical.TOP -> listOf(BEFORE, BEFORE, BEFORE, BEFORE)
+            TestVertical.MIDDLE_TOP -> listOf(ON, ON, BEFORE, BEFORE)
+            TestVertical.MIDDLE -> listOf(AFTER, AFTER, BEFORE, BEFORE)
+            TestVertical.MIDDLE_BOTTOM -> listOf(AFTER, AFTER, ON, ON)
+            TestVertical.BOTTOM -> listOf(AFTER, AFTER, AFTER, AFTER)
+        }
+
+    /** Calls [getTextFieldSelectionLayout] to get a [SelectionLayout]. */
+    @OptIn(ExperimentalContracts::class)
+    internal fun buildSelectionLayoutForTest(
+        startHandlePosition: Offset = Offset(5f, 5f),
+        endHandlePosition: Offset = Offset(25f, 5f),
+        previousHandlePosition: Offset = Offset.Unspecified,
+        containerCoordinates: LayoutCoordinates = MockCoordinates(),
+        isStartHandle: Boolean = false,
+        previousSelection: Selection? = null,
+        selectableIdOrderingComparator: Comparator<Long> = naturalOrder(),
+        block: SelectionLayoutBuilder.() -> Unit,
+    ): SelectionLayout {
+        contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
+        return SelectionLayoutBuilder(
+            startHandlePosition = startHandlePosition,
+            endHandlePosition = endHandlePosition,
+            previousHandlePosition = previousHandlePosition,
+            containerCoordinates = containerCoordinates,
+            isStartHandle = isStartHandle,
+            previousSelection = previousSelection,
+            selectableIdOrderingComparator = selectableIdOrderingComparator,
+        ).run {
+            block()
+            build()
+        }
+    }
+
+    internal fun SelectionLayoutBuilder.appendInfoForTest(
+        selectableId: Long = 1L,
+        text: String = "hello",
+        rawStartHandleOffset: Int = 0,
+        startXHandleDirection: Direction = ON,
+        startYHandleDirection: Direction = ON,
+        rawEndHandleOffset: Int = 5,
+        endXHandleDirection: Direction = ON,
+        endYHandleDirection: Direction = ON,
+        rawPreviousHandleOffset: Int = -1,
+        rtlRanges: List<IntRange> = emptyList(),
+        wordBoundaries: List<TextRange> = listOf(),
+        lineBreaks: List<Int> = emptyList(),
+    ): SelectableInfo {
+        val layoutResult = getTextLayoutResultMock(
+            text = text,
+            rtlCharRanges = rtlRanges,
+            wordBoundaries = wordBoundaries,
+            lineBreaks = lineBreaks,
+        )
+        return appendInfo(
+            selectableId = selectableId,
+            rawStartHandleOffset = rawStartHandleOffset,
+            startXHandleDirection = startXHandleDirection,
+            startYHandleDirection = startYHandleDirection,
+            rawEndHandleOffset = rawEndHandleOffset,
+            endXHandleDirection = endXHandleDirection,
+            endYHandleDirection = endYHandleDirection,
+            rawPreviousHandleOffset = rawPreviousHandleOffset,
+            textLayoutResult = layoutResult
+        )
+    }
+}
+
+@SmallTest
+@RunWith(Parameterized::class)
+class SelectionLayoutStartSlot2DTest(
+    private val vertical: TestVertical,
+    private val horizontal: TestHorizontal,
+    private val expectedSlot: Int,
+) : SelectionLayout2DTest() {
+    companion object {
+        @JvmStatic
+        @Parameters(name = "verticalPosition={0}, horizontalPosition={1} expectedSlot={2}")
+        fun data(): Collection<Array<Any>> = listOf(
+            arrayOf(TestVertical.TOP, TestHorizontal.LEFT, 0),
+            arrayOf(TestVertical.TOP, TestHorizontal.MIDDLE_LEFT, 0),
+            arrayOf(TestVertical.TOP, TestHorizontal.MIDDLE, 0),
+            arrayOf(TestVertical.TOP, TestHorizontal.MIDDLE_RIGHT, 0),
+            arrayOf(TestVertical.TOP, TestHorizontal.RIGHT, 0),
+            arrayOf(TestVertical.MIDDLE_TOP, TestHorizontal.LEFT, 0),
+            arrayOf(TestVertical.MIDDLE_TOP, TestHorizontal.MIDDLE_LEFT, 1),
+            arrayOf(TestVertical.MIDDLE_TOP, TestHorizontal.MIDDLE, 2),
+            arrayOf(TestVertical.MIDDLE_TOP, TestHorizontal.MIDDLE_RIGHT, 3),
+            arrayOf(TestVertical.MIDDLE_TOP, TestHorizontal.RIGHT, 4),
+            arrayOf(TestVertical.MIDDLE, TestHorizontal.LEFT, 4),
+            arrayOf(TestVertical.MIDDLE, TestHorizontal.MIDDLE_LEFT, 4),
+            arrayOf(TestVertical.MIDDLE, TestHorizontal.MIDDLE, 4),
+            arrayOf(TestVertical.MIDDLE, TestHorizontal.MIDDLE_RIGHT, 4),
+            arrayOf(TestVertical.MIDDLE, TestHorizontal.RIGHT, 4),
+            arrayOf(TestVertical.MIDDLE_BOTTOM, TestHorizontal.LEFT, 4),
+            arrayOf(TestVertical.MIDDLE_BOTTOM, TestHorizontal.MIDDLE_LEFT, 5),
+            arrayOf(TestVertical.MIDDLE_BOTTOM, TestHorizontal.MIDDLE, 6),
+            arrayOf(TestVertical.MIDDLE_BOTTOM, TestHorizontal.MIDDLE_RIGHT, 7),
+            arrayOf(TestVertical.MIDDLE_BOTTOM, TestHorizontal.RIGHT, 8),
+            arrayOf(TestVertical.BOTTOM, TestHorizontal.LEFT, 8),
+            arrayOf(TestVertical.BOTTOM, TestHorizontal.MIDDLE_LEFT, 8),
+            arrayOf(TestVertical.BOTTOM, TestHorizontal.MIDDLE, 8),
+            arrayOf(TestVertical.BOTTOM, TestHorizontal.MIDDLE_RIGHT, 8),
+            arrayOf(TestVertical.BOTTOM, TestHorizontal.RIGHT, 8),
+        )
+    }
+
+    // Test the start slot. end slot handle directions will always point to the 4th selectable.
+    @Test
+    fun startSlot2dTest() {
+        val (xDirection1, xDirection2, xDirection3, xDirection4) = getDirectionsForX(horizontal)
+        val (yDirection1, yDirection2, yDirection3, yDirection4) = getDirectionsForY(vertical)
+        val layout = buildSelectionLayoutForTest {
+            appendInfoForTest(
+                selectableId = 1L,
+                startXHandleDirection = xDirection1,
+                startYHandleDirection = yDirection1,
+                endXHandleDirection = AFTER,
+                endYHandleDirection = AFTER,
+            )
+            appendInfoForTest(
+                selectableId = 2L,
+                startXHandleDirection = xDirection2,
+                startYHandleDirection = yDirection2,
+                endXHandleDirection = ON,
+                endYHandleDirection = AFTER,
+            )
+            appendInfoForTest(
+                selectableId = 3L,
+                startXHandleDirection = xDirection3,
+                startYHandleDirection = yDirection3,
+                endXHandleDirection = AFTER,
+                endYHandleDirection = ON,
+            )
+            appendInfoForTest(
+                selectableId = 4L,
+                startXHandleDirection = xDirection4,
+                startYHandleDirection = yDirection4,
+                endXHandleDirection = ON,
+                endYHandleDirection = ON,
+            )
+        }
+        assertThat(layout.endSlot).isEqualTo(7)
+        assertThat(layout.startSlot).isEqualTo(expectedSlot)
+    }
+}
+
+@SmallTest
+@RunWith(Parameterized::class)
+class SelectionLayoutEndSlot2DTest(
+    private val vertical: TestVertical,
+    private val horizontal: TestHorizontal,
+    private val expectedSlot: Int,
+) : SelectionLayout2DTest() {
+    companion object {
+        @JvmStatic
+        @Parameters(name = "verticalPosition={0}, horizontalPosition={1} expectedSlot={2}")
+        fun data(): Collection<Array<Any>> = listOf(
+            arrayOf(TestVertical.TOP, TestHorizontal.LEFT, 0),
+            arrayOf(TestVertical.TOP, TestHorizontal.MIDDLE_LEFT, 0),
+            arrayOf(TestVertical.TOP, TestHorizontal.MIDDLE, 0),
+            arrayOf(TestVertical.TOP, TestHorizontal.MIDDLE_RIGHT, 0),
+            arrayOf(TestVertical.TOP, TestHorizontal.RIGHT, 0),
+            arrayOf(TestVertical.MIDDLE_TOP, TestHorizontal.LEFT, 0),
+            arrayOf(TestVertical.MIDDLE_TOP, TestHorizontal.MIDDLE_LEFT, 1),
+            arrayOf(TestVertical.MIDDLE_TOP, TestHorizontal.MIDDLE, 2),
+            arrayOf(TestVertical.MIDDLE_TOP, TestHorizontal.MIDDLE_RIGHT, 3),
+            arrayOf(TestVertical.MIDDLE_TOP, TestHorizontal.RIGHT, 4),
+            arrayOf(TestVertical.MIDDLE, TestHorizontal.LEFT, 4),
+            arrayOf(TestVertical.MIDDLE, TestHorizontal.MIDDLE_LEFT, 4),
+            arrayOf(TestVertical.MIDDLE, TestHorizontal.MIDDLE, 4),
+            arrayOf(TestVertical.MIDDLE, TestHorizontal.MIDDLE_RIGHT, 4),
+            arrayOf(TestVertical.MIDDLE, TestHorizontal.RIGHT, 4),
+            arrayOf(TestVertical.MIDDLE_BOTTOM, TestHorizontal.LEFT, 4),
+            arrayOf(TestVertical.MIDDLE_BOTTOM, TestHorizontal.MIDDLE_LEFT, 5),
+            arrayOf(TestVertical.MIDDLE_BOTTOM, TestHorizontal.MIDDLE, 6),
+            arrayOf(TestVertical.MIDDLE_BOTTOM, TestHorizontal.MIDDLE_RIGHT, 7),
+            arrayOf(TestVertical.MIDDLE_BOTTOM, TestHorizontal.RIGHT, 8),
+            arrayOf(TestVertical.BOTTOM, TestHorizontal.LEFT, 8),
+            arrayOf(TestVertical.BOTTOM, TestHorizontal.MIDDLE_LEFT, 8),
+            arrayOf(TestVertical.BOTTOM, TestHorizontal.MIDDLE, 8),
+            arrayOf(TestVertical.BOTTOM, TestHorizontal.MIDDLE_RIGHT, 8),
+            arrayOf(TestVertical.BOTTOM, TestHorizontal.RIGHT, 8),
+        )
+    }
+
+    // Test the end slot. start slot handle directions will always point to the 1st selectable.
+    @Test
+    fun endSlot2dTest() {
+        val (xDirection1, xDirection2, xDirection3, xDirection4) = getDirectionsForX(horizontal)
+        val (yDirection1, yDirection2, yDirection3, yDirection4) = getDirectionsForY(vertical)
+        val layout = buildSelectionLayoutForTest {
+            appendInfoForTest(
+                selectableId = 1L,
+                startXHandleDirection = ON,
+                startYHandleDirection = ON,
+                endXHandleDirection = xDirection1,
+                endYHandleDirection = yDirection1,
+            )
+            appendInfoForTest(
+                selectableId = 2L,
+                startXHandleDirection = BEFORE,
+                startYHandleDirection = ON,
+                endXHandleDirection = xDirection2,
+                endYHandleDirection = yDirection2,
+            )
+            appendInfoForTest(
+                selectableId = 3L,
+                startXHandleDirection = ON,
+                startYHandleDirection = BEFORE,
+                endXHandleDirection = xDirection3,
+                endYHandleDirection = yDirection3,
+            )
+            appendInfoForTest(
+                selectableId = 4L,
+                startXHandleDirection = BEFORE,
+                startYHandleDirection = BEFORE,
+                endXHandleDirection = xDirection4,
+                endYHandleDirection = yDirection4,
+            )
+        }
+        assertThat(layout.startSlot).isEqualTo(1)
+        assertThat(layout.endSlot).isEqualTo(expectedSlot)
+    }
+}
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionLayoutTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionLayoutTest.kt
index 16c6157..3cba9f6 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionLayoutTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionLayoutTest.kt
@@ -16,6 +16,9 @@
 
 package androidx.compose.foundation.text.selection
 
+import androidx.compose.foundation.text.selection.Direction.AFTER
+import androidx.compose.foundation.text.selection.Direction.BEFORE
+import androidx.compose.foundation.text.selection.Direction.ON
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.text.TextRange
@@ -93,16 +96,16 @@
         val selection = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                endHandleDirection = Direction.AFTER,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 3L,
-                startHandleDirection = Direction.BEFORE,
+                startYHandleDirection = BEFORE,
             )
         }
         assertThat(selection.size).isEqualTo(3)
@@ -121,13 +124,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.startSlot).isEqualTo(0)
@@ -138,13 +141,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.startSlot).isEqualTo(1)
@@ -155,13 +158,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.startSlot).isEqualTo(2)
@@ -172,13 +175,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = ON,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.startSlot).isEqualTo(3)
@@ -189,13 +192,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.startSlot).isEqualTo(4)
@@ -214,13 +217,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = ON,
+                endYHandleDirection = BEFORE,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = BEFORE,
             )
         }
         assertThat(layout.endSlot).isEqualTo(0)
@@ -231,13 +234,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = ON,
+                endYHandleDirection = ON,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = BEFORE,
             )
         }
         assertThat(layout.endSlot).isEqualTo(1)
@@ -248,13 +251,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = BEFORE,
             )
         }
         assertThat(layout.endSlot).isEqualTo(2)
@@ -265,13 +268,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.endSlot).isEqualTo(3)
@@ -282,13 +285,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = AFTER,
             )
         }
         assertThat(layout.endSlot).isEqualTo(4)
@@ -326,8 +329,8 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = ON,
+                endYHandleDirection = ON,
                 rawStartHandleOffset = 0,
                 rawEndHandleOffset = 0,
             )
@@ -340,13 +343,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = ON,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = ON,
+                endYHandleDirection = BEFORE,
             )
         }
         assertThat(layout.crossStatus).isEqualTo(CrossStatus.CROSSED)
@@ -357,13 +360,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.crossStatus).isEqualTo(CrossStatus.NOT_CROSSED)
@@ -377,13 +380,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.startInfo.selectableId).isEqualTo(1L)
@@ -394,13 +397,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.startInfo.selectableId).isEqualTo(1L)
@@ -411,13 +414,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.startInfo.selectableId).isEqualTo(2L)
@@ -428,13 +431,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = ON,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.startInfo.selectableId).isEqualTo(2L)
@@ -445,13 +448,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.startInfo.selectableId).isEqualTo(2L)
@@ -465,13 +468,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = ON,
+                endYHandleDirection = BEFORE,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = BEFORE,
             )
         }
         assertThat(layout.endInfo.selectableId).isEqualTo(1L)
@@ -482,13 +485,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = ON,
+                endYHandleDirection = ON,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = BEFORE,
             )
         }
         assertThat(layout.endInfo.selectableId).isEqualTo(1L)
@@ -499,13 +502,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = BEFORE,
             )
         }
         assertThat(layout.endInfo.selectableId).isEqualTo(1L)
@@ -516,13 +519,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.endInfo.selectableId).isEqualTo(2L)
@@ -533,13 +536,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = AFTER,
             )
         }
         assertThat(layout.endInfo.selectableId).isEqualTo(2L)
@@ -550,13 +553,13 @@
         val layout = buildSelectionLayoutForTest(isStartHandle = true) {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.currentInfo.selectableId).isEqualTo(1L)
@@ -567,13 +570,13 @@
         val layout = buildSelectionLayoutForTest(isStartHandle = false) {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.currentInfo.selectableId).isEqualTo(2L)
@@ -584,13 +587,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.firstInfo.selectableId).isEqualTo(1L)
@@ -601,13 +604,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = ON,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = ON,
+                endYHandleDirection = BEFORE,
             )
         }
         assertThat(layout.firstInfo.selectableId).isEqualTo(1L)
@@ -618,13 +621,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.lastInfo.selectableId).isEqualTo(2L)
@@ -635,13 +638,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = ON,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = ON,
+                endYHandleDirection = BEFORE,
             )
         }
         assertThat(layout.lastInfo.selectableId).isEqualTo(2L)
@@ -660,13 +663,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
 
@@ -681,18 +684,18 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             info = appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 3L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         val infoList = mutableListOf<SelectableInfo>()
@@ -707,23 +710,23 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             infoOne = appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = AFTER,
             )
             infoTwo = appendInfoForTest(
                 selectableId = 3L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 4L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         val infoList = mutableListOf<SelectableInfo>()
@@ -849,14 +852,14 @@
             appendInfoForTest(
                 rawEndHandleOffset = 5,
                 rawPreviousHandleOffset = 5,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 rawEndHandleOffset = 5,
                 rawPreviousHandleOffset = 5,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         val otherLayout = buildSelectionLayoutForTest(
@@ -865,14 +868,14 @@
             appendInfoForTest(
                 rawEndHandleOffset = 5,
                 rawPreviousHandleOffset = 5,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 rawEndHandleOffset = 5,
                 rawPreviousHandleOffset = 5,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.shouldRecomputeSelection(otherLayout)).isTrue()
@@ -886,14 +889,14 @@
             appendInfoForTest(
                 rawEndHandleOffset = 5,
                 rawPreviousHandleOffset = 5,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 rawEndHandleOffset = 5,
                 rawPreviousHandleOffset = 5,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = AFTER,
             )
         }
         val otherLayout = buildSelectionLayoutForTest(
@@ -902,14 +905,14 @@
             appendInfoForTest(
                 rawEndHandleOffset = 5,
                 rawPreviousHandleOffset = 5,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 rawEndHandleOffset = 5,
                 rawPreviousHandleOffset = 5,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         assertThat(layout.shouldRecomputeSelection(otherLayout)).isTrue()
@@ -1069,13 +1072,13 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         val selection = getSelection(startSelectableId = 1L, endSelectableId = 2L)
@@ -1090,18 +1093,18 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 3L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         val selection = getSelection(startSelectableId = 1L, endSelectableId = 3L)
@@ -1117,23 +1120,23 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = ON,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 3L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.AFTER,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = AFTER,
             )
             appendInfoForTest(
                 selectableId = 4L,
-                startHandleDirection = Direction.BEFORE,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = BEFORE,
+                endYHandleDirection = ON,
             )
         }
         val selection = getSelection(startSelectableId = 1L, endSelectableId = 4L)
@@ -1150,8 +1153,8 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = ON,
                 rawStartHandleOffset = 5,
                 rawEndHandleOffset = 0,
             )
@@ -1171,15 +1174,15 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = ON,
                 rawStartHandleOffset = 5,
                 rawEndHandleOffset = 0,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = ON,
+                endYHandleDirection = BEFORE,
                 rawStartHandleOffset = 5,
                 rawEndHandleOffset = 0,
             )
@@ -1216,22 +1219,22 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = ON,
                 rawStartHandleOffset = 5,
                 rawEndHandleOffset = 0,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = BEFORE,
                 rawStartHandleOffset = 5,
                 rawEndHandleOffset = 0,
             )
             appendInfoForTest(
                 selectableId = 3L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = ON,
+                endYHandleDirection = BEFORE,
                 rawStartHandleOffset = 5,
                 rawEndHandleOffset = 0,
             )
@@ -1276,29 +1279,29 @@
         val layout = buildSelectionLayoutForTest {
             appendInfoForTest(
                 selectableId = 1L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.ON,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = ON,
                 rawStartHandleOffset = 5,
                 rawEndHandleOffset = 0,
             )
             appendInfoForTest(
                 selectableId = 2L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = BEFORE,
                 rawStartHandleOffset = 5,
                 rawEndHandleOffset = 0,
             )
             appendInfoForTest(
                 selectableId = 3L,
-                startHandleDirection = Direction.AFTER,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = AFTER,
+                endYHandleDirection = BEFORE,
                 rawStartHandleOffset = 5,
                 rawEndHandleOffset = 0,
             )
             appendInfoForTest(
                 selectableId = 4L,
-                startHandleDirection = Direction.ON,
-                endHandleDirection = Direction.BEFORE,
+                startYHandleDirection = ON,
+                endYHandleDirection = BEFORE,
                 rawStartHandleOffset = 5,
                 rawEndHandleOffset = 0,
             )
@@ -1544,9 +1547,11 @@
         selectableId: Long = 1L,
         text: String = "hello",
         rawStartHandleOffset: Int = 0,
-        startHandleDirection: Direction = Direction.ON,
+        startXHandleDirection: Direction = ON,
+        startYHandleDirection: Direction = ON,
         rawEndHandleOffset: Int = 5,
-        endHandleDirection: Direction = Direction.ON,
+        endXHandleDirection: Direction = ON,
+        endYHandleDirection: Direction = ON,
         rawPreviousHandleOffset: Int = -1,
         rtlRanges: List<IntRange> = emptyList(),
         wordBoundaries: List<TextRange> = listOf(),
@@ -1561,9 +1566,11 @@
         return appendInfo(
             selectableId = selectableId,
             rawStartHandleOffset = rawStartHandleOffset,
-            startHandleDirection = startHandleDirection,
+            startXHandleDirection = startXHandleDirection,
+            startYHandleDirection = startYHandleDirection,
             rawEndHandleOffset = rawEndHandleOffset,
-            endHandleDirection = endHandleDirection,
+            endXHandleDirection = endXHandleDirection,
+            endYHandleDirection = endYHandleDirection,
             rawPreviousHandleOffset = rawPreviousHandleOffset,
             textLayoutResult = layoutResult
         )
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/CodepointTransformationTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/CodepointTransformationTest.kt
new file mode 100644
index 0000000..21ef966
--- /dev/null
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/CodepointTransformationTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text2.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text2.input.internal.OffsetMappingCalculator
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlin.test.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalFoundationApi::class)
+@RunWith(JUnit4::class)
+class CodepointTransformationTest {
+
+    @Test
+    fun toVisualText_codepointIndices() {
+        val source =
+            TextFieldCharSequence("a${SurrogateCodepointString}c$SurrogateCodepointString")
+        val offsetMapping = OffsetMappingCalculator()
+        val codepointTransformation = CodepointTransformation { i, codepoint ->
+            val expectedCodePoint = when (i) {
+                0 -> 'a'.code
+                1 -> SurrogateCodepoint
+                2 -> 'c'.code
+                3 -> SurrogateCodepoint
+                else -> fail("Invalid codepoint index: $i")
+            }
+            assertThat(codepoint).isEqualTo(expectedCodePoint)
+            codepoint
+        }
+
+        source.toVisualText(codepointTransformation, offsetMapping)
+    }
+
+    @Test
+    fun toVisualText_mapsOffsetsForward() {
+        val source = TextFieldCharSequence("a${SurrogateCodepointString}c")
+        val offsetMapping = OffsetMappingCalculator()
+        val codepointTransformation = CodepointTransformation { i, codepoint ->
+            when (codepoint) {
+                'a'.code, 'c'.code -> SurrogateCodepoint
+                SurrogateCodepoint -> 'b'.code
+                else -> fail(
+                    "codepointIndex=$i, codepoint=\"${
+                        String(intArrayOf(codepoint), 0, 1)
+                    }\""
+                )
+            }
+        }
+        val visual = source.toVisualText(codepointTransformation, offsetMapping)
+
+        assertThat(visual.toString())
+            .isEqualTo("${SurrogateCodepointString}b$SurrogateCodepointString")
+
+        listOf(
+            0 to TextRange(0),
+            1 to TextRange(2),
+            2 to TextRange(2, 3),
+            3 to TextRange(3),
+            4 to TextRange(5),
+        ).forEach { (source, dest) ->
+            assertWithMessage("Mapping from untransformed offset $source")
+                .that(offsetMapping.mapFromSource(source)).isEqualTo(dest)
+        }
+    }
+
+    @Test
+    fun toVisualText_mapsOffsetsBackward() {
+        val source = TextFieldCharSequence("a${SurrogateCodepointString}c")
+        val offsetMapping = OffsetMappingCalculator()
+        val codepointTransformation = CodepointTransformation { i, codepoint ->
+            when (codepoint) {
+                'a'.code, 'c'.code -> SurrogateCodepoint
+                SurrogateCodepoint -> 'b'.code
+                else -> fail(
+                    "codepointIndex=$i, codepoint=\"${
+                        String(intArrayOf(codepoint), 0, 1)
+                    }\""
+                )
+            }
+        }
+        val visual = source.toVisualText(codepointTransformation, offsetMapping)
+
+        assertThat(visual.toString())
+            .isEqualTo("${SurrogateCodepointString}b$SurrogateCodepointString")
+
+        listOf(
+            0 to TextRange(0),
+            1 to TextRange(0, 1),
+            2 to TextRange(1),
+            3 to TextRange(3),
+            4 to TextRange(3, 4),
+            5 to TextRange(4),
+        ).forEach { (dest, source) ->
+            assertWithMessage("Mapping from transformed offset $dest")
+                .that(offsetMapping.mapFromDest(dest)).isEqualTo(source)
+        }
+    }
+
+    private companion object {
+        /** This is "𐐷", a surrogate codepoint. */
+        val SurrogateCodepoint = Character.toCodePoint('\uD801', '\uDC37')
+        const val SurrogateCodepointString = "\uD801\uDC37"
+    }
+}
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTrackerTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTrackerTest.kt
index 5c08053..3029a8e 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTrackerTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTrackerTest.kt
@@ -190,7 +190,7 @@
         val changes = ChangeTracker()
 
         fun append(text: String) {
-            changes.trackChange(TextRange(builder.length), text.length)
+            changes.trackChange(builder.length, builder.length, text.length)
             builder.append(text)
         }
 
@@ -198,7 +198,7 @@
             val start = builder.indexOf(substring)
             if (start != -1) {
                 val end = start + substring.length
-                changes.trackChange(TextRange(start, end), text.length)
+                changes.trackChange(start, end, text.length)
                 builder.replace(start, end, text)
             }
         }
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/internal/OffsetMappingCalculatorTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/internal/OffsetMappingCalculatorTest.kt
new file mode 100644
index 0000000..782f6b6
--- /dev/null
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/internal/OffsetMappingCalculatorTest.kt
@@ -0,0 +1,902 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text2.input.internal
+
+import androidx.compose.ui.text.TextRange
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class OffsetMappingCalculatorTest {
+
+    @Test
+    fun noChanges() {
+        val builder = TestEditBuffer()
+        builder.assertIdentityMapping()
+    }
+
+    @Test
+    fun insertCharIntoEmpty() {
+        val builder = TestEditBuffer()
+        builder.append("a")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0, 1),
+            1 to TextRange(2),
+            2 to TextRange(3),
+            3 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0),
+            2 to TextRange(1),
+        )
+    }
+
+    @Test
+    fun insertCharIntoMiddle() {
+        val builder = TestEditBuffer("ab")
+        builder.insert(1, "c")
+        assertThat(builder.toString()).isEqualTo("acb")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1, 2),
+            2 to TextRange(3),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1),
+            3 to TextRange(2),
+        )
+    }
+
+    @Test
+    fun deleteCharFromMiddle() {
+        val builder = TestEditBuffer("abc")
+        builder.delete(1)
+        assertThat(builder.toString()).isEqualTo("ac")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1),
+            3 to TextRange(2),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1, 2),
+            2 to TextRange(3),
+            3 to TextRange(4),
+        )
+    }
+
+    @Test
+    fun replaceCharInMiddle() {
+        val builder = TestEditBuffer("abc")
+        builder.replace(1, "d")
+        assertThat(builder.toString()).isEqualTo("adc")
+        builder.assertIdentityMapping()
+    }
+
+    @Test
+    fun insertStringIntoEmpty() {
+        val builder = TestEditBuffer("")
+        builder.append("ab")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0, 2),
+            1 to TextRange(3),
+            2 to TextRange(4),
+            3 to TextRange(5),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0),
+            2 to TextRange(0),
+            3 to TextRange(1),
+        )
+    }
+
+    @Test
+    fun insertStringIntoMiddle() {
+        val builder = TestEditBuffer("ab")
+        builder.insert(1, "cd")
+        assertThat(builder.toString()).isEqualTo("acdb")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1, 3),
+            2 to TextRange(4),
+            3 to TextRange(5),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1),
+            3 to TextRange(1),
+            4 to TextRange(2),
+            5 to TextRange(3),
+        )
+    }
+
+    @Test
+    fun deleteStringFromMiddle() {
+        val builder = TestEditBuffer("abcd")
+        builder.delete(1, 3)
+        assertThat(builder.toString()).isEqualTo("ad")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1),
+            3 to TextRange(1),
+            4 to TextRange(2),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1, 3),
+            2 to TextRange(4),
+            3 to TextRange(5),
+        )
+    }
+
+    @Test
+    fun replaceStringWithEqualLengthInMiddle() {
+        val builder = TestEditBuffer("abcd")
+        builder.replace(1, 3, "ef")
+        assertThat(builder.toString()).isEqualTo("aefd")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1, 3),
+            3 to TextRange(3),
+            4 to TextRange(4),
+            5 to TextRange(5),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1, 3),
+            3 to TextRange(3),
+            4 to TextRange(4),
+            5 to TextRange(5),
+        )
+    }
+
+    @Test
+    fun replaceStringWithLongerInMiddle() {
+        val builder = TestEditBuffer("abcd")
+        builder.replace(1, 3, "efg")
+        assertThat(builder.toString()).isEqualTo("aefgd")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1, 4),
+            3 to TextRange(4),
+            4 to TextRange(5),
+            5 to TextRange(6),
+            6 to TextRange(7),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1, 3),
+            3 to TextRange(1, 3),
+            4 to TextRange(3),
+            5 to TextRange(4),
+        )
+    }
+
+    @Test
+    fun replaceStringWithShorterInMiddle() {
+        val builder = TestEditBuffer("abcd")
+        builder.replace(1, 3, "e")
+        assertThat(builder.toString()).isEqualTo("aed")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1, 2),
+            3 to TextRange(2),
+            4 to TextRange(3),
+            5 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(3),
+            3 to TextRange(4),
+            4 to TextRange(5),
+            5 to TextRange(6),
+        )
+    }
+
+    @Test
+    fun replaceAllWithEqualLength() {
+        val builder = TestEditBuffer("abcd")
+        builder.replace("efgh")
+        assertThat(builder.toString()).isEqualTo("efgh")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0, 4),
+            2 to TextRange(0, 4),
+            3 to TextRange(0, 4),
+            4 to TextRange(4),
+            5 to TextRange(5),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0, 4),
+            2 to TextRange(0, 4),
+            3 to TextRange(0, 4),
+            4 to TextRange(4),
+            5 to TextRange(5),
+        )
+    }
+
+    @Test
+    fun replaceAllWithLonger() {
+        val builder = TestEditBuffer("abcd")
+        builder.replace("efghi")
+        assertThat(builder.toString()).isEqualTo("efghi")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0, 5),
+            2 to TextRange(0, 5),
+            3 to TextRange(0, 5),
+            4 to TextRange(5),
+            5 to TextRange(6),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0, 4),
+            2 to TextRange(0, 4),
+            3 to TextRange(0, 4),
+            4 to TextRange(0, 4),
+            5 to TextRange(4),
+            6 to TextRange(5),
+        )
+    }
+
+    @Test
+    fun replaceAllWithShorter() {
+        val builder = TestEditBuffer("abcd")
+        builder.replace("ef")
+        assertThat(builder.toString()).isEqualTo("ef")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0, 2),
+            2 to TextRange(0, 2),
+            3 to TextRange(0, 2),
+            4 to TextRange(2),
+            5 to TextRange(3),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0, 4),
+            2 to TextRange(4),
+            3 to TextRange(5),
+            4 to TextRange(6),
+            5 to TextRange(7),
+        )
+    }
+
+    @Test
+    fun prependCharToString() {
+        val builder = TestEditBuffer("a")
+        builder.insert(0, "b")
+        assertThat(builder.toString()).isEqualTo("ba")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0, 1),
+            1 to TextRange(2),
+            2 to TextRange(3),
+            3 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0),
+            2 to TextRange(1),
+        )
+    }
+
+    @Test
+    fun prependStringToString() {
+        val builder = TestEditBuffer("a")
+        builder.insert(0, "bc")
+        assertThat(builder.toString()).isEqualTo("bca")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0, 2),
+            1 to TextRange(3),
+            2 to TextRange(4),
+            3 to TextRange(5),
+            4 to TextRange(6),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0),
+            2 to TextRange(0),
+            3 to TextRange(1),
+        )
+    }
+
+    @Test
+    fun appendCharToString() {
+        val builder = TestEditBuffer("a")
+        builder.append("b")
+        assertThat(builder.toString()).isEqualTo("ab")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1, 2),
+            2 to TextRange(3),
+            3 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1),
+            3 to TextRange(2),
+        )
+    }
+
+    @Test
+    fun appendStringToString() {
+        val builder = TestEditBuffer("a")
+        builder.append("bc")
+        assertThat(builder.toString()).isEqualTo("abc")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1, 3),
+            2 to TextRange(4),
+            3 to TextRange(5),
+            4 to TextRange(6),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1),
+            3 to TextRange(1),
+            4 to TextRange(2),
+        )
+    }
+
+    @Test
+    fun multiplePrepends() {
+        val builder = TestEditBuffer("ab")
+        builder.insert(0, "c")
+        builder.insert(0, "d")
+        builder.insert(0, "ef")
+        assertThat(builder.toString()).isEqualTo("efdcab")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0, 4),
+            1 to TextRange(5),
+            2 to TextRange(6),
+            3 to TextRange(7),
+            4 to TextRange(8),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0),
+            2 to TextRange(0),
+            3 to TextRange(0),
+            4 to TextRange(0),
+            5 to TextRange(1),
+            6 to TextRange(2),
+            7 to TextRange(3),
+        )
+    }
+
+    @Test
+    fun multipleAppends() {
+        val builder = TestEditBuffer("ab")
+        builder.append("c")
+        builder.append("d")
+        builder.append("ef")
+        assertThat(builder.toString()).isEqualTo("abcdef")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(2, 6),
+            3 to TextRange(7),
+            4 to TextRange(8),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(2),
+            3 to TextRange(2),
+            4 to TextRange(2),
+            5 to TextRange(2),
+            6 to TextRange(2),
+            7 to TextRange(3),
+        )
+    }
+
+    @Test
+    fun multiplePrependsThenDeletesCancellingOut() {
+        val builder = TestEditBuffer("ab")
+        builder.insert(0, "cde") // cdeab
+        builder.delete(2) // cdab
+        builder.delete(0) // dab
+        builder.insert(0, "f") // fdab
+        builder.delete(0, 2)
+        assertThat(builder.toString()).isEqualTo("ab")
+        builder.assertIdentityMapping()
+    }
+
+    @Test
+    fun multipleAppendsThenDeletesCancellingOut() {
+        val builder = TestEditBuffer("ab")
+        builder.append("cde") // abcde
+        builder.delete(2) // abde
+        builder.delete(3) // abd
+        builder.append("f") // abdf
+        builder.delete(2, 4)
+        assertThat(builder.toString()).isEqualTo("ab")
+        builder.assertIdentityMapping()
+    }
+
+    @Test
+    fun multipleInsertsThenDeletesCancellingOut() {
+        val builder = TestEditBuffer("ab")
+        builder.insert(1, "c")
+        builder.insert(2, "de")
+        builder.insert(1, "f")
+        builder.delete(1, 3)
+        builder.delete(1)
+        builder.delete(1)
+        assertThat(builder.toString()).isEqualTo("ab")
+        builder.assertIdentityMapping()
+    }
+
+    @Test
+    fun multipleContinuousDeletesAtStartInOrder() {
+        val builder = TestEditBuffer("abcdef")
+        builder.delete(0)
+        builder.delete(0)
+        builder.delete(0)
+        assertThat(builder.toString()).isEqualTo("def")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0),
+            2 to TextRange(0),
+            3 to TextRange(0),
+            4 to TextRange(1),
+            5 to TextRange(2),
+            6 to TextRange(3),
+            7 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0, 3),
+            1 to TextRange(4),
+            2 to TextRange(5),
+            3 to TextRange(6),
+            4 to TextRange(7),
+        )
+    }
+
+    @Test
+    fun multipleContinuousDeletesAtStartOutOfOrder() {
+        val builder = TestEditBuffer("abcdef")
+        builder.delete(1)
+        builder.delete(1)
+        builder.delete(0)
+        assertThat(builder.toString()).isEqualTo("def")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0),
+            2 to TextRange(0),
+            3 to TextRange(0),
+            4 to TextRange(1),
+            5 to TextRange(2),
+            6 to TextRange(3),
+            7 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0, 3),
+            1 to TextRange(4),
+            2 to TextRange(5),
+            3 to TextRange(6),
+            4 to TextRange(7),
+        )
+    }
+
+    @Test
+    fun multipleContinuousDeletesAtEndInOrder() {
+        val builder = TestEditBuffer("abcdef")
+        builder.delete(builder.length - 1)
+        builder.delete(builder.length - 1)
+        builder.delete(builder.length - 1)
+        assertThat(builder.toString()).isEqualTo("abc")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(2),
+            3 to TextRange(3),
+            4 to TextRange(3),
+            5 to TextRange(3),
+            6 to TextRange(3),
+            7 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(2),
+            3 to TextRange(3, 6),
+            4 to TextRange(7),
+            5 to TextRange(8),
+        )
+    }
+
+    @Test
+    fun multipleContinuousDeletesAtEndOutOfOrder() {
+        val builder = TestEditBuffer("abcdef")
+        builder.delete(4)
+        builder.delete(3)
+        builder.delete(3)
+        assertThat(builder.toString()).isEqualTo("abc")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(2),
+            3 to TextRange(3),
+            4 to TextRange(3),
+            5 to TextRange(3),
+            6 to TextRange(3),
+            7 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(2),
+            3 to TextRange(3, 6),
+            4 to TextRange(7),
+            5 to TextRange(8),
+        )
+    }
+
+    @Test
+    fun multipleContinuousDeletesInMiddleInOrder() {
+        val builder = TestEditBuffer("abcdef")
+        builder.delete(1)
+        builder.delete(1, 3)
+        builder.delete(1)
+        assertThat(builder.toString()).isEqualTo("af")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1),
+            3 to TextRange(1),
+            4 to TextRange(1),
+            5 to TextRange(1),
+            6 to TextRange(2),
+            7 to TextRange(3),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1, 5),
+            2 to TextRange(6),
+            3 to TextRange(7),
+            4 to TextRange(8),
+            5 to TextRange(9),
+        )
+    }
+
+    @Test
+    fun multipleContinuousDeletesInMiddleOutOfOrder() {
+        val builder = TestEditBuffer("abcdef")
+        builder.delete(2)
+        builder.delete(2, 4)
+        builder.delete(1)
+        assertThat(builder.toString()).isEqualTo("af")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(1),
+            2 to TextRange(1),
+            3 to TextRange(1),
+            4 to TextRange(1),
+            5 to TextRange(1),
+            6 to TextRange(2),
+            7 to TextRange(3),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(1, 5),
+            2 to TextRange(6),
+            3 to TextRange(7),
+            4 to TextRange(8),
+            5 to TextRange(9),
+        )
+    }
+
+    @Test
+    fun discontinuousInsertsAndDeletes() {
+        val builder = TestEditBuffer("ab")
+        builder.insert(1, "cde") // acdeb
+        builder.delete(2) // aceb
+        builder.append("fgh") // acebfgh
+        builder.delete(4) // acebgh
+        builder.insert(0, "ijk") // ijkacebgh
+        builder.delete(2) // ijacebgh
+        assertThat(builder.toString()).isEqualTo("ijacebgh")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0, 2),
+            1 to TextRange(3, 5),
+            2 to TextRange(6, 8),
+            3 to TextRange(9),
+            4 to TextRange(10),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0),
+            2 to TextRange(0),
+            3 to TextRange(1),
+            4 to TextRange(1),
+            5 to TextRange(1),
+            6 to TextRange(2),
+            7 to TextRange(2),
+            8 to TextRange(2),
+            9 to TextRange(3),
+        )
+    }
+
+    @Test
+    fun multipleContinuousOneToOneReplacements() {
+        val builder = TestEditBuffer("abc")
+        builder.replace(0, "f")
+        builder.replace(1, "f")
+        builder.replace(2, "f")
+        assertThat(builder.toString()).isEqualTo("fff")
+        builder.assertIdentityMapping()
+    }
+
+    /** This simulates an expanding codepoint transform. */
+    @Test
+    fun multipleContinuousOneToManyReplacements() {
+        val builder = TestEditBuffer("abc")
+        builder.replace(0, "dd") // ddbc
+        builder.replace(2, "ee") // ddeec
+        builder.replace(4, "ff")
+        assertThat(builder.toString()).isEqualTo("ddeeff")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(2),
+            2 to TextRange(4),
+            3 to TextRange(6),
+            4 to TextRange(7),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0, 1),
+            2 to TextRange(1),
+            3 to TextRange(1, 2),
+            4 to TextRange(2),
+            5 to TextRange(2, 3),
+            6 to TextRange(3),
+            7 to TextRange(4),
+        )
+    }
+
+    @Test
+    fun multipleContinuousOneToManyReplacementsReversed() {
+        val builder = TestEditBuffer("abc")
+        builder.replace(2, "dd") // abdd
+        builder.replace(1, "ee") // aeedd
+        builder.replace(0, "ff")
+        assertThat(builder.toString()).isEqualTo("ffeedd")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(2),
+            2 to TextRange(4),
+            3 to TextRange(6),
+            4 to TextRange(7),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0, 1),
+            2 to TextRange(1),
+            3 to TextRange(1, 2),
+            4 to TextRange(2),
+            5 to TextRange(2, 3),
+            6 to TextRange(3),
+            7 to TextRange(4),
+        )
+    }
+
+    /** This simulates a contracting codepoint transform. */
+    @Test
+    fun multipleContinuousManyToOneReplacements() {
+        val builder = TestEditBuffer("abcdef")
+        builder.replace(0, 2, "g") // gcdef
+        builder.replace(1, 3, "h") // ghef
+        builder.replace(2, 4, "i")
+        assertThat(builder.toString()).isEqualTo("ghi")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0, 1),
+            2 to TextRange(1),
+            3 to TextRange(1, 2),
+            4 to TextRange(2),
+            5 to TextRange(2, 3),
+            6 to TextRange(3),
+            7 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(2),
+            2 to TextRange(4),
+            3 to TextRange(6),
+            4 to TextRange(7),
+        )
+    }
+
+    @Test
+    fun multipleContinuousManyToOneReplacementsReversed() {
+        val builder = TestEditBuffer("abcdef")
+        builder.replace(4, 6, "g") // abcdg
+        builder.replace(2, 4, "h") // abhg
+        builder.replace(0, 2, "i")
+        assertThat(builder.toString()).isEqualTo("ihg")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0, 1),
+            2 to TextRange(1),
+            3 to TextRange(1, 2),
+            4 to TextRange(2),
+            5 to TextRange(2, 3),
+            6 to TextRange(3),
+            7 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(2),
+            2 to TextRange(4),
+            3 to TextRange(6),
+            4 to TextRange(7),
+        )
+    }
+
+    /**
+     * This sequence of operations is basically nonsense and so the mappings don't make much sense
+     * either. This test is just here to ensure the output is consistent and doesn't crash.
+     */
+    @Test
+    fun twoOverlappingReplacements() {
+        val builder = TestEditBuffer("abc")
+        builder.replace(0, 2, "wx") // wxc
+        builder.replace(1, 3, "yz")
+        assertThat(builder.toString()).isEqualTo("wyz")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0, 3),
+            2 to TextRange(1, 3),
+            3 to TextRange(3),
+            4 to TextRange(4),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0, 2),
+            2 to TextRange(0, 3),
+            3 to TextRange(3),
+            4 to TextRange(4),
+        )
+    }
+
+    /**
+     * This sequence of operations is basically nonsense and so the mappings don't make much sense
+     * either. This test is just here to ensure the output is consistent and doesn't crash.
+     */
+    @Test
+    fun fourOverlappingReplacementsReversed() {
+        val builder = TestEditBuffer("abcde")
+        builder.replace(1, 3, "fg") // afgde
+        builder.replace(2, 4, "hi") // afhie
+        builder.replace(0, 2, "jk") // jkhie
+        builder.replace(3, 5, "lm")
+        assertThat(builder.toString()).isEqualTo("jkhlm")
+        builder.assertMappingsFromSource(
+            0 to TextRange(0),
+            1 to TextRange(0, 2),
+            2 to TextRange(0, 5),
+            3 to TextRange(2, 5),
+            4 to TextRange(3, 5),
+            5 to TextRange(5),
+            6 to TextRange(6),
+            7 to TextRange(7),
+        )
+        builder.assertMappingsFromDest(
+            0 to TextRange(0),
+            1 to TextRange(0, 3),
+            2 to TextRange(1, 3),
+            3 to TextRange(1, 4),
+            4 to TextRange(1, 5),
+            5 to TextRange(5),
+            6 to TextRange(6),
+            7 to TextRange(7),
+        )
+    }
+
+    private fun TestEditBuffer.assertIdentityMapping() {
+        // Check well off the end of the valid index range just to be sure.
+        repeat(length + 2) {
+            assertWithMessage("Mapping from source offset $it")
+                .that(mapFromSource(it)).isEqualTo(TextRange(it))
+            assertWithMessage("Mapping from dest offset $it")
+                .that(mapFromDest(it)).isEqualTo(TextRange(it))
+        }
+    }
+
+    private fun TestEditBuffer.assertMappingsFromSource(
+        vararg expectedMappings: Pair<Int, TextRange>
+    ) {
+        expectedMappings.forEach { (srcOffset, dstRange) ->
+            assertWithMessage("Mapping from source offset $srcOffset")
+                .that(mapFromSource(srcOffset)).isEqualTo(dstRange)
+        }
+    }
+
+    private fun TestEditBuffer.assertMappingsFromDest(
+        vararg expectedMappings: Pair<Int, TextRange>
+    ) {
+        expectedMappings.forEach { (dstOffset, srcRange) ->
+            assertWithMessage("Mapping from dest offset $dstOffset")
+                .that(mapFromDest(dstOffset)).isEqualTo(srcRange)
+        }
+    }
+
+    /**
+     * Basic implementation of a text editing buffer that uses [OffsetMappingCalculator] to make
+     * testing easier.
+     */
+    private class TestEditBuffer private constructor(
+        private val builder: StringBuilder
+    ) : CharSequence by builder {
+        constructor(text: CharSequence = "") : this(StringBuilder(text))
+
+        private val tracker = OffsetMappingCalculator()
+
+        fun append(text: CharSequence) {
+            tracker.recordEditOperation(length, length, text.length)
+            builder.append(text)
+        }
+
+        fun insert(offset: Int, value: CharSequence) {
+            tracker.recordEditOperation(offset, offset, value.length)
+            builder.insert(offset, value)
+        }
+
+        fun delete(start: Int, end: Int = start + 1) {
+            tracker.recordEditOperation(start, end, 0)
+            builder.delete(minOf(start, end), maxOf(start, end))
+        }
+
+        fun replace(value: String) {
+            replace(0, length, value)
+        }
+
+        fun replace(start: Int, value: String) {
+            replace(start, start + 1, value)
+        }
+
+        fun replace(start: Int, end: Int, value: String) {
+            tracker.recordEditOperation(start, end, value.length)
+            builder.replace(minOf(start, end), maxOf(start, end), value)
+        }
+
+        fun mapFromSource(offset: Int): TextRange = tracker.mapFromSource(offset)
+        fun mapFromDest(offset: Int): TextRange = tracker.mapFromDest(offset)
+
+        override fun toString(): String = builder.toString()
+    }
+}
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/res/layout/activity_view_carousel.xml b/compose/integration-tests/macrobenchmark-target/src/main/res/layout/activity_view_carousel.xml
index 61e862c..9b22250 100644
--- a/compose/integration-tests/macrobenchmark-target/src/main/res/layout/activity_view_carousel.xml
+++ b/compose/integration-tests/macrobenchmark-target/src/main/res/layout/activity_view_carousel.xml
@@ -22,7 +22,7 @@
     android:layout_width="match_parent"
     android:layout_height="400dp">
 
-    <androidx.viewpager2.widget.ViewPager2
+    <androidx.recyclerview.widget.RecyclerView
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/carousel"
         android:layout_gravity="center"
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/res/layout/recycler_view_as_carousel_item.xml b/compose/integration-tests/macrobenchmark-target/src/main/res/layout/recycler_view_as_carousel_item.xml
index 03eb6bd..5b5aa07 100644
--- a/compose/integration-tests/macrobenchmark-target/src/main/res/layout/recycler_view_as_carousel_item.xml
+++ b/compose/integration-tests/macrobenchmark-target/src/main/res/layout/recycler_view_as_carousel_item.xml
@@ -17,7 +17,7 @@
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="200dp"
     android:background="#000000"
-    android:layout_height="200dp"
+    android:layout_height="match_parent"
     android:layout_gravity="center">
 
     <TextView
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/PagerAsCarouselBenchmark.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/PagerAsCarouselBenchmark.kt
index becf364..f6b8b9c 100644
--- a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/PagerAsCarouselBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/PagerAsCarouselBenchmark.kt
@@ -29,6 +29,7 @@
 import androidx.test.uiautomator.Until
 import androidx.testutils.createCompilationParams
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -51,6 +52,7 @@
     }
 
     @Test
+    @Ignore("b/299155975")
     fun scroll() {
         val carousel = device.findObject(By.desc(ContentDescription))
         benchmarkRule.performRepeatedScroll(PackageName, compilationMode, Action, carousel) {
diff --git a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/RecyclerViewAsCarouselBenchmark.kt b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/RecyclerViewAsCarouselBenchmark.kt
index 2152280..0316577 100644
--- a/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/RecyclerViewAsCarouselBenchmark.kt
+++ b/compose/integration-tests/macrobenchmark/src/main/java/androidx/compose/integration/macrobenchmark/RecyclerViewAsCarouselBenchmark.kt
@@ -24,7 +24,6 @@
 import androidx.test.uiautomator.UiDevice
 import androidx.testutils.createCompilationParams
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -46,7 +45,6 @@
     }
 
     @Test
-    @Ignore("b/297398943")
     fun scroll() {
         val carousel = device.findObject(
             By.res(
diff --git a/compose/lint/internal-lint-checks/build.gradle b/compose/lint/internal-lint-checks/build.gradle
index 3039214..f2132be 100644
--- a/compose/lint/internal-lint-checks/build.gradle
+++ b/compose/lint/internal-lint-checks/build.gradle
@@ -26,6 +26,7 @@
     compileOnly(libs.androidLintApi)
     compileOnly(libs.kotlinStdlib)
     implementation(project(":compose:lint:common"))
+    implementation(project(":collection:collection"))
 
     testImplementation(project(":compose:lint:common-test"))
     testImplementation(libs.kotlinStdlib)
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/AsCollectionDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/AsCollectionDetector.kt
new file mode 100644
index 0000000..2d470e0
--- /dev/null
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/AsCollectionDetector.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.compose.lint
+
+import androidx.collection.MutableObjectList
+import androidx.collection.MutableScatterMap
+import androidx.collection.MutableScatterSet
+import androidx.collection.ObjectList
+import androidx.collection.ScatterMap
+import androidx.collection.ScatterSet
+import androidx.collection.scatterSetOf
+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.impl.source.PsiClassReferenceType
+import java.util.EnumSet
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+
+/**
+ * Using [ScatterMap.asMap], [ScatterSet.asSet], [ObjectList.asList], or their mutable
+ * counterparts indicates that the developer may be using the collection incorrectly.
+ * Using the interfaces is slower access. It is best to use those only for when it touches
+ * public API.
+ */
+class AsCollectionDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableUastTypes() = listOf<Class<out UElement>>(
+        UCallExpression::class.java
+    )
+
+    override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
+        override fun visitCallExpression(node: UCallExpression) {
+            val methodName = node.methodName ?: return
+            if (methodName in MethodNames) {
+                val receiverType = node.receiverType as? PsiClassReferenceType ?: return
+                val qualifiedName = receiverType.reference.qualifiedName ?: return
+                val indexOfAngleBracket = qualifiedName.indexOf('<')
+                if (indexOfAngleBracket > 0 &&
+                    qualifiedName.substring(0, indexOfAngleBracket) in CollectionClasses
+                ) {
+                    context.report(
+                        ISSUE,
+                        node,
+                        context.getLocation(node),
+                        "Use method $methodName() only for public API usage"
+                    )
+                }
+            }
+        }
+    }
+
+    companion object {
+        private val MethodNames = scatterSetOf(
+            "asMap",
+            "asMutableMap",
+            "asSet",
+            "asMutableSet",
+            "asList",
+            "asMutableList"
+        )
+        private val CollectionClasses = scatterSetOf(
+            ScatterMap::class.qualifiedName,
+            MutableScatterMap::class.qualifiedName,
+            ScatterSet::class.qualifiedName,
+            MutableScatterSet::class.qualifiedName,
+            ObjectList::class.qualifiedName,
+            MutableObjectList::class.qualifiedName,
+        )
+
+        private val AsCollectionDetectorId = "AsCollectionCall"
+
+        val ISSUE = Issue.create(
+            id = AsCollectionDetectorId,
+            briefDescription = "High performance collections don't implement standard collection " +
+                "interfaces so that they can remain high performance. Converting to standard " +
+                "collections wraps the classes with another object. Use these interface " +
+                "wrappers only for exposing to public API.",
+            explanation = "ScatterMap, ScatterSet, and AnyList are written for high " +
+                "performance access. Using the standard collection interfaces for these classes " +
+                "forces slower performance access to these collections. The methods returning " +
+                "these interfaces should be limited to public API, where standard collection " +
+                "interfaces are expected.",
+            category = Category.PERFORMANCE, priority = 3, severity = Severity.ERROR,
+            implementation = Implementation(
+                AsCollectionDetector::class.java,
+                EnumSet.of(Scope.JAVA_FILE)
+            )
+        )
+    }
+}
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
index 7e43f93..f13628f 100644
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
@@ -28,6 +28,7 @@
     override val api = 14
     override val issues get(): List<Issue> {
         return listOf(
+            AsCollectionDetector.ISSUE,
             ExceptionMessageDetector.ISSUE,
             ListIteratorDetector.ISSUE,
             SteppedForLoopDetector.ISSUE,
diff --git a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/AsCollectionDetectorTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/AsCollectionDetectorTest.kt
new file mode 100644
index 0000000..39ce2a1
--- /dev/null
+++ b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/AsCollectionDetectorTest.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.lint
+
+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.Parameterized
+
+/* ktlint-disable max-line-length */
+@RunWith(Parameterized::class)
+class AsCollectionDetectorTest(
+    val types: CollectionType
+) : LintDetectorTest() {
+
+    override fun getDetector(): Detector = AsCollectionDetector()
+
+    override fun getIssues(): MutableList<Issue> =
+        mutableListOf(AsCollectionDetector.ISSUE)
+
+    private val collectionTilde = "~".repeat(types.collection.length)
+
+    @Test
+    fun immutableAsImmutable() {
+        lint().files(
+            ScatterMapClass,
+            ScatterSetClass,
+            ObjectListClass,
+            kotlin(
+                """
+                        package androidx.compose.lint
+
+                        import androidx.collection.${types.immutable}
+
+                        fun foo(collection: ${types.immutable}${types.params}): ${types.collection}${types.params} =
+                            collection.as${types.collection}()
+                        """
+            )
+        ).run().expect(
+            """
+src/androidx/compose/lint/test.kt:7: Error: Use method as${types.collection}() only for public API usage [AsCollectionCall]
+                            collection.as${types.collection}()
+                            ~~~~~~~~~~~~~$collectionTilde~~
+1 errors, 0 warnings
+            """
+        )
+    }
+
+    @Test
+    fun mutableAsImmutable() {
+        lint().files(
+            ScatterMapClass,
+            ScatterSetClass,
+            ObjectListClass,
+            kotlin(
+                """
+                        package androidx.compose.lint
+
+                        import androidx.collection.Mutable${types.immutable}
+
+                        fun foo(collection: Mutable${types.immutable}${types.params}): ${types.collection}${types.params} =
+                            collection.as${types.collection}()
+                        """
+            )
+        ).run().expect(
+            """
+src/androidx/compose/lint/test.kt:7: Error: Use method as${types.collection}() only for public API usage [AsCollectionCall]
+                            collection.as${types.collection}()
+                            ~~~~~~~~~~~~~$collectionTilde~~
+1 errors, 0 warnings
+            """
+        )
+    }
+
+    @Test
+    fun mutableAsMutable() {
+        lint().files(
+            ScatterMapClass,
+            ScatterSetClass,
+            ObjectListClass,
+            kotlin(
+                """
+                        package androidx.compose.lint
+
+                        import androidx.collection.Mutable${types.immutable}
+
+                        fun foo(collection: Mutable${types.immutable}${types.params}): Mutable${types.collection}${types.params} =
+                            collection.asMutable${types.collection}()
+                        """
+            )
+        ).run().expect(
+            """
+src/androidx/compose/lint/test.kt:7: Error: Use method asMutable${types.collection}() only for public API usage [AsCollectionCall]
+                            collection.asMutable${types.collection}()
+                            ~~~~~~~~~~~~~~~~~~~~$collectionTilde~~
+1 errors, 0 warnings
+            """
+        )
+    }
+
+    @Test
+    fun nonCollectionAs() {
+        lint().files(
+            kotlin(
+                """
+                        package androidx.compose.lint
+
+                        fun foo(): ${types.collection}${types.params} =
+                            WeirdCollection().as${types.collection}()
+
+                        class WeirdCollection {
+                            fun asList(): List<String>? = null
+                            fun asSet(): Set<String>? = null
+                            fun asMap(): Map<String, String>? = null
+                        }
+                        """
+            )
+        ).run().expectClean()
+    }
+
+    class CollectionType(
+        val immutable: String,
+        val collection: String,
+        val params: String
+    )
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun initParameters() = listOf(
+            CollectionType("ScatterMap", "Map", "<String, String>"),
+            CollectionType("ScatterSet", "Set", "<String>"),
+            CollectionType("ObjectList", "List", "<String>")
+        )
+
+        val ScatterMapClass = kotlin(
+            """
+            package androidx.collection
+            sealed class ScatterMap<K, V> {
+                fun asMap(): Map<K, V> = mapOf()
+            }
+
+            class MutableScatterMap<K, V> : ScatterMap<K, V>() {
+                fun asMutableMap(): MutableMap<K, V> = mutableMapOf()
+            }
+            """.trimIndent()
+        )
+
+        val ScatterSetClass = kotlin(
+            """
+            package androidx.collection
+            sealed class ScatterSet<E> {
+                fun asSet(): Set<E> = setOf()
+            }
+
+            class MutableScatterSet<E> : ScatterSet<E>() {
+                fun asMutableSet(): MutableSet<E> = mutableSetOf()
+            }
+            """.trimIndent()
+        )
+
+        val ObjectListClass = kotlin(
+            """
+            package androidx.collection
+            sealed class ObjectList<E> {
+                fun asList(): List<E> = listOf()
+            }
+
+            class MutableObjectList<E> : ObjectList<E>() {
+                fun asMutableList(): MutableList<E> = mutableListOf()
+            }
+            """.trimIndent()
+        )
+    }
+}
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index d319950..989002c 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -660,7 +660,10 @@
   public final class ScaffoldKt {
     method @androidx.compose.runtime.Composable public static void Scaffold(androidx.compose.foundation.layout.WindowInsets contentWindowInsets, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.ScaffoldState scaffoldState, optional kotlin.jvm.functions.Function0<kotlin.Unit> topBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> bottomBar, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost, optional kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional int floatingActionButtonPosition, optional boolean isFloatingActionButtonDocked, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent, optional boolean drawerGesturesEnabled, optional androidx.compose.ui.graphics.Shape drawerShape, optional float drawerElevation, optional long drawerBackgroundColor, optional long drawerContentColor, optional long drawerScrimColor, optional long backgroundColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void Scaffold(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.ScaffoldState scaffoldState, optional kotlin.jvm.functions.Function0<kotlin.Unit> topBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> bottomBar, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost, optional kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional int floatingActionButtonPosition, optional boolean isFloatingActionButtonDocked, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent, optional boolean drawerGesturesEnabled, optional androidx.compose.ui.graphics.Shape drawerShape, optional float drawerElevation, optional long drawerBackgroundColor, optional long drawerContentColor, optional long drawerScrimColor, optional long backgroundColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static boolean getScaffoldSubcomposeInMeasureFix();
     method @androidx.compose.runtime.Composable public static androidx.compose.material.ScaffoldState rememberScaffoldState(optional androidx.compose.material.DrawerState drawerState, optional androidx.compose.material.SnackbarHostState snackbarHostState);
+    method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static void setScaffoldSubcomposeInMeasureFix(boolean);
+    property @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static final boolean ScaffoldSubcomposeInMeasureFix;
   }
 
   @androidx.compose.runtime.Stable public final class ScaffoldState {
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index d319950..989002c 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -660,7 +660,10 @@
   public final class ScaffoldKt {
     method @androidx.compose.runtime.Composable public static void Scaffold(androidx.compose.foundation.layout.WindowInsets contentWindowInsets, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.ScaffoldState scaffoldState, optional kotlin.jvm.functions.Function0<kotlin.Unit> topBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> bottomBar, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost, optional kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional int floatingActionButtonPosition, optional boolean isFloatingActionButtonDocked, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent, optional boolean drawerGesturesEnabled, optional androidx.compose.ui.graphics.Shape drawerShape, optional float drawerElevation, optional long drawerBackgroundColor, optional long drawerContentColor, optional long drawerScrimColor, optional long backgroundColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void Scaffold(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.ScaffoldState scaffoldState, optional kotlin.jvm.functions.Function0<kotlin.Unit> topBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> bottomBar, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost, optional kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional int floatingActionButtonPosition, optional boolean isFloatingActionButtonDocked, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent, optional boolean drawerGesturesEnabled, optional androidx.compose.ui.graphics.Shape drawerShape, optional float drawerElevation, optional long drawerBackgroundColor, optional long drawerContentColor, optional long drawerScrimColor, optional long backgroundColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static boolean getScaffoldSubcomposeInMeasureFix();
     method @androidx.compose.runtime.Composable public static androidx.compose.material.ScaffoldState rememberScaffoldState(optional androidx.compose.material.DrawerState drawerState, optional androidx.compose.material.SnackbarHostState snackbarHostState);
+    method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static void setScaffoldSubcomposeInMeasureFix(boolean);
+    property @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static final boolean ScaffoldSubcomposeInMeasureFix;
   }
 
   @androidx.compose.runtime.Stable public final class ScaffoldState {
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ScaffoldTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ScaffoldTest.kt
index c77c4e8..85e9194 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ScaffoldTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ScaffoldTest.kt
@@ -39,6 +39,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.LookaheadScope
 import androidx.compose.ui.layout.SubcomposeLayout
@@ -67,8 +68,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import kotlin.math.roundToInt
 import kotlinx.coroutines.runBlocking
 import org.junit.Ignore
@@ -356,7 +357,8 @@
         }
         with(rule.density) {
             assertThat(fabPosition.x).isWithin(1f).of(
-                (rule.rootWidth().toPx() - fabSize.width) / 2f)
+                (rule.rootWidth().toPx() - fabSize.width) / 2f
+            )
         }
         val expectedFabY = bottomBarPosition.y - (fabSize.height / 2)
         assertThat(fabPosition.y).isEqualTo(expectedFabY)
@@ -396,7 +398,8 @@
         }
         with(rule.density) {
             assertThat(fabPosition.x).isWithin(1f).of(
-                rule.rootWidth().toPx() - fabSize.width - fabSpacing.toPx())
+                rule.rootWidth().toPx() - fabSize.width - fabSpacing.toPx()
+            )
         }
         val expectedFabY = bottomBarPosition.y - (fabSize.height / 2)
         assertThat(fabPosition.y).isEqualTo(expectedFabY)
@@ -415,7 +418,8 @@
                 Scaffold(
                     topBar = {
                         Box(
-                            Modifier.requiredSize(10.dp)
+                            Modifier
+                                .requiredSize(10.dp)
                                 .shadow(4.dp)
                                 .zIndex(4f)
                                 .background(color = Color.White)
@@ -423,7 +427,8 @@
                     }
                 ) {
                     Box(
-                        Modifier.requiredSize(10.dp)
+                        Modifier
+                            .requiredSize(10.dp)
                             .background(color = Color.White)
                     )
                 }
@@ -491,9 +496,11 @@
             val animatedFab = @Composable {
                 AnimatedVisibility(visible = showFab.value) {
                     FloatingActionButton(
-                        modifier = Modifier.onGloballyPositioned { positioned ->
-                            actualFabSize = positioned.size
-                        }.testTag(fabTestTag),
+                        modifier = Modifier
+                            .onGloballyPositioned { positioned ->
+                                actualFabSize = positioned.size
+                            }
+                            .testTag(fabTestTag),
                         onClick = {}
                     ) {
                         Icon(Icons.Filled.Favorite, null)
@@ -768,7 +775,7 @@
 
                         layout(constraints.maxWidth, constraints.maxHeight) {
                             onPlaceCount++
-                            Truth.assertWithMessage("Expected onSizeChangedCount to be >= 1")
+                            assertWithMessage("Expected onSizeChangedCount to be >= 1")
                                 .that(onSizeChangedCount).isAtLeast(1)
                             assertThat(size).isNotNull()
                             placeables.forEach { it.place(0, 0) }
@@ -778,7 +785,73 @@
             }
         }
 
-        Truth.assertWithMessage("Expected placeCount to be >= 1").that(onPlaceCount).isAtLeast(1)
+        assertWithMessage("Expected placeCount to be >= 1").that(onPlaceCount).isAtLeast(1)
+    }
+
+    @OptIn(ExperimentalMaterialApi::class)
+    @Test
+    fun scaffold_subcomposeInMeasureFix_enabled_measuresChildrenInMeasurement() {
+        ScaffoldSubcomposeInMeasureFix = true
+        var size: IntSize? = null
+        var measured = false
+        rule.setContent {
+            Layout(
+                content = {
+                    Scaffold(
+                        content = {
+                            Box(Modifier.onSizeChanged { size = it })
+                        }
+                    )
+                }
+            ) { measurables, constraints ->
+                measurables.map { it.measure(constraints) }
+                measured = true
+                layout(0, 0) {
+                    // Empty measurement since we only care about placement
+                }
+            }
+        }
+
+        assertWithMessage("Measure should have been executed")
+            .that(measured).isTrue()
+        assertWithMessage("Expected size to be initialized")
+            .that(size).isNotNull()
+    }
+
+    @OptIn(ExperimentalMaterialApi::class)
+    @Test
+    fun scaffold_subcomposeInMeasureFix_disabled_measuresChildrenInPlacement() {
+        ScaffoldSubcomposeInMeasureFix = false
+        var size: IntSize? = null
+        var measured = false
+        var placed = false
+        rule.setContent {
+            Layout(
+                content = {
+                    Scaffold(
+                        content = {
+                            Box(Modifier.onSizeChanged { size = it })
+                        }
+                    )
+                }
+            ) { measurables, constraints ->
+                val placeables = measurables.map { it.measure(constraints) }
+                measured = true
+                assertWithMessage("Expected size to not be initialized in placement")
+                    .that(size).isNull()
+                layout(constraints.maxWidth, constraints.maxHeight) {
+                    placeables.forEach { it.place(0, 0) }
+                    placed = true
+                }
+            }
+        }
+
+        assertWithMessage("Measure should have been executed")
+            .that(measured).isTrue()
+        assertWithMessage("Placement should have been executed")
+            .that(placed).isTrue()
+        assertWithMessage("Expected size to be initialized")
+            .that(size).isNotNull()
     }
 
     private fun assertDpIsWithinThreshold(actual: Dp, expected: Dp, threshold: Dp) {
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 106f8ad..41eaced 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
@@ -78,7 +81,7 @@
 /**
  * The possible positions for a [FloatingActionButton] attached to a [Scaffold].
  */
-@kotlin.jvm.JvmInline
+@JvmInline
 value class FabPosition internal constructor(@Suppress("unused") private val value: Int) {
     companion object {
         /**
@@ -364,6 +367,22 @@
 }
 
 /**
+ * Flag indicating if [Scaffold] should subcompose and measure its children during measurement or
+ * during placement.
+ * Set this flag to false to keep Scaffold's old measurement behavior (measuring in placement).
+ *
+ * <b>This flag will be removed in Compose 1.6.0-beta01.</b> If you encounter any issues with the
+ * new behavior, please file an issue at: issuetracker.google.com/issues/new?component=742043
+ */
+// TODO(b/299621062): Remove flag before beta
+@Suppress("GetterSetterNames", "OPT_IN_MARKER_ON_WRONG_TARGET")
+@get:Suppress("GetterSetterNames")
+@get:ExperimentalMaterialApi
+@set:ExperimentalMaterialApi
+@ExperimentalMaterialApi
+var ScaffoldSubcomposeInMeasureFix by mutableStateOf(true)
+
+/**
  * Layout for a [Scaffold]'s content.
  *
  * @param isFabDocked whether the FAB (if present) is docked to the bottom bar or not
@@ -376,6 +395,7 @@
  * @param bottomBar the content to place at the bottom of the [Scaffold], on top of the
  * [content], typically a [BottomAppBar].
  */
+@OptIn(ExperimentalMaterialApi::class)
 @Composable
 @UiComposable
 private fun ScaffoldLayout(
@@ -388,6 +408,46 @@
     contentWindowInsets: WindowInsets,
     bottomBar: @Composable @UiComposable () -> Unit
 ) {
+    if (ScaffoldSubcomposeInMeasureFix) {
+        ScaffoldLayoutWithMeasureFix(
+            isFabDocked = isFabDocked,
+            fabPosition = fabPosition,
+            topBar = topBar,
+            content = content,
+            snackbar = snackbar,
+            fab = fab,
+            contentWindowInsets = contentWindowInsets,
+            bottomBar = bottomBar
+        )
+    } else {
+        LegacyScaffoldLayout(
+            isFabDocked = isFabDocked,
+            fabPosition = fabPosition,
+            topBar = topBar,
+            content = content,
+            snackbar = snackbar,
+            fab = fab,
+            contentWindowInsets = contentWindowInsets,
+            bottomBar = bottomBar
+        )
+    }
+}
+
+/**
+ * Layout for a [Scaffold]'s content, subcomposing and measuring during measurement.
+ */
+@Composable
+@UiComposable
+private fun ScaffoldLayoutWithMeasureFix(
+    isFabDocked: Boolean,
+    fabPosition: FabPosition,
+    topBar: @Composable @UiComposable () -> Unit,
+    content: @Composable @UiComposable (PaddingValues) -> Unit,
+    snackbar: @Composable @UiComposable () -> Unit,
+    fab: @Composable @UiComposable () -> Unit,
+    contentWindowInsets: WindowInsets,
+    bottomBar: @Composable @UiComposable () -> Unit
+) {
     SubcomposeLayout { constraints ->
         val layoutWidth = constraints.maxWidth
         val layoutHeight = constraints.maxHeight
@@ -447,6 +507,7 @@
                             layoutWidth - FabSpacing.roundToPx() - fabWidth
                         }
                     }
+
                     FabPosition.End -> {
                         if (layoutDirection == LayoutDirection.Ltr) {
                             layoutWidth - FabSpacing.roundToPx() - fabWidth
@@ -454,6 +515,7 @@
                             FabSpacing.roundToPx()
                         }
                     }
+
                     else -> (layoutWidth - fabWidth) / 2
                 }
 
@@ -550,6 +612,183 @@
 }
 
 /**
+ * Legacy layout for a [Scaffold]'s content, subcomposing and measuring during placement.
+ */
+@Composable
+@UiComposable
+private fun LegacyScaffoldLayout(
+    isFabDocked: Boolean,
+    fabPosition: FabPosition,
+    topBar: @Composable @UiComposable () -> Unit,
+    content: @Composable @UiComposable (PaddingValues) -> Unit,
+    snackbar: @Composable @UiComposable () -> Unit,
+    fab: @Composable @UiComposable () -> Unit,
+    contentWindowInsets: WindowInsets,
+    bottomBar: @Composable @UiComposable () -> Unit
+) {
+    SubcomposeLayout { constraints ->
+        val layoutWidth = constraints.maxWidth
+        val layoutHeight = constraints.maxHeight
+
+        val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
+
+        layout(layoutWidth, layoutHeight) {
+            val topBarPlaceables = subcompose(ScaffoldLayoutContent.TopBar, topBar).fastMap {
+                it.measure(looseConstraints)
+            }
+
+            val topBarHeight = topBarPlaceables.fastMaxBy { it.height }?.height ?: 0
+
+            val snackbarPlaceables = subcompose(ScaffoldLayoutContent.Snackbar, snackbar).fastMap {
+                // respect only bottom and horizontal for snackbar and fab
+                val leftInset = contentWindowInsets
+                    .getLeft(this@SubcomposeLayout, layoutDirection)
+                val rightInset = contentWindowInsets
+                    .getRight(this@SubcomposeLayout, layoutDirection)
+                val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout)
+                // offset the snackbar constraints by the insets values
+                it.measure(
+                    looseConstraints.offset(
+                        -leftInset - rightInset,
+                        -bottomInset
+                    )
+                )
+            }
+
+            val snackbarHeight = snackbarPlaceables.fastMaxBy { it.height }?.height ?: 0
+
+            val fabPlaceables =
+                subcompose(ScaffoldLayoutContent.Fab, fab).fastMap { measurable ->
+                    // respect only bottom and horizontal for snackbar and fab
+                    val leftInset =
+                        contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection)
+                    val rightInset =
+                        contentWindowInsets.getRight(this@SubcomposeLayout, layoutDirection)
+                    val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout)
+                    measurable.measure(
+                        looseConstraints.offset(
+                            -leftInset - rightInset,
+                            -bottomInset
+                        )
+                    )
+                }
+
+            val fabPlacement = if (fabPlaceables.isNotEmpty()) {
+                val fabWidth = fabPlaceables.fastMaxBy { it.width }?.width ?: 0
+                val fabHeight = fabPlaceables.fastMaxBy { it.height }?.height ?: 0
+                // FAB distance from the left of the layout, taking into account LTR / RTL
+                if (fabWidth != 0 && fabHeight != 0) {
+                    val fabLeftOffset = when (fabPosition) {
+                        FabPosition.Start -> {
+                            if (layoutDirection == LayoutDirection.Ltr) {
+                                FabSpacing.roundToPx()
+                            } else {
+                                layoutWidth - FabSpacing.roundToPx() - fabWidth
+                            }
+                        }
+
+                        FabPosition.End -> {
+                            if (layoutDirection == LayoutDirection.Ltr) {
+                                layoutWidth - FabSpacing.roundToPx() - fabWidth
+                            } else {
+                                FabSpacing.roundToPx()
+                            }
+                        }
+
+                        else -> (layoutWidth - fabWidth) / 2
+                    }
+
+                    FabPlacement(
+                        isDocked = isFabDocked,
+                        left = fabLeftOffset,
+                        width = fabWidth,
+                        height = fabHeight
+                    )
+                } else {
+                    null
+                }
+            } else {
+                null
+            }
+
+            val bottomBarPlaceables = subcompose(ScaffoldLayoutContent.BottomBar) {
+                CompositionLocalProvider(
+                    LocalFabPlacement provides fabPlacement,
+                    content = bottomBar
+                )
+            }.fastMap { it.measure(looseConstraints) }
+
+            val bottomBarHeight = bottomBarPlaceables.fastMaxBy { it.height }?.height
+            val fabOffsetFromBottom = fabPlacement?.let {
+                if (bottomBarHeight == null) {
+                    it.height + FabSpacing.roundToPx() +
+                        contentWindowInsets.getBottom(this@SubcomposeLayout)
+                } else {
+                    if (isFabDocked) {
+                        // Total height is the bottom bar height + half the FAB height
+                        bottomBarHeight + (it.height / 2)
+                    } else {
+                        // Total height is the bottom bar height + the FAB height + the padding
+                        // between the FAB and bottom bar
+                        bottomBarHeight + it.height + FabSpacing.roundToPx()
+                    }
+                }
+            }
+
+            val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
+                snackbarHeight +
+                    (fabOffsetFromBottom ?: bottomBarHeight
+                    ?: contentWindowInsets.getBottom(this@SubcomposeLayout))
+            } else {
+                0
+            }
+
+            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)
+            }.fastMap { it.measure(looseConstraints.copy(maxHeight = bodyContentHeight)) }
+
+            // Placing to control drawing order to match default elevation of each placeable
+            bodyContentPlaceables.fastForEach {
+                it.place(0, topBarHeight)
+            }
+            topBarPlaceables.fastForEach {
+                it.place(0, 0)
+            }
+            snackbarPlaceables.fastForEach {
+                it.place(0, layoutHeight - snackbarOffsetFromBottom)
+            }
+            // The bottom bar is always at the bottom of the layout
+            bottomBarPlaceables.fastForEach {
+                it.place(0, layoutHeight - (bottomBarHeight ?: 0))
+            }
+            // Explicitly not using placeRelative here as `leftOffset` already accounts for RTL
+            fabPlaceables.fastForEach {
+                it.place(fabPlacement?.left ?: 0, layoutHeight - (fabOffsetFromBottom ?: 0))
+            }
+        }
+    }
+}
+
+/**
  * Placement information for a [FloatingActionButton] inside a [Scaffold].
  *
  * @property isDocked whether the FAB should be docked with the bottom bar
diff --git a/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/ColorSchemeBenchmark.kt b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/ColorSchemeBenchmark.kt
new file mode 100644
index 0000000..20e04e7
--- /dev/null
+++ b/compose/material3/benchmark/src/androidTest/java/androidx/compose/material3/benchmark/ColorSchemeBenchmark.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3.benchmark
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.contentColorFor
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class ColorSchemeBenchmark {
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    private val colorSchemeTestCaseFactory = { ColorSchemeTestCase() }
+
+    @Test
+    fun firstPixel() {
+        benchmarkRule.benchmarkToFirstPixel(colorSchemeTestCaseFactory)
+    }
+}
+
+class ColorSchemeTestCase : LayeredComposeTestCase() {
+
+    @Composable
+    override fun MeasuredContent() {
+        Column {
+            Box(modifier = Modifier.size(1.dp).background(MaterialTheme.colorScheme.surface))
+            Box(modifier = Modifier.size(1.dp).background(
+                MaterialTheme.colorScheme.contentColorFor(MaterialTheme.colorScheme.surface))
+            )
+            Box(modifier = Modifier.size(1.dp).background(MaterialTheme.colorScheme.primary))
+            Box(modifier = Modifier.size(1.dp).background(
+                MaterialTheme.colorScheme.contentColorFor(MaterialTheme.colorScheme.primary))
+            )
+            Box(modifier = Modifier.size(1.dp).background(MaterialTheme.colorScheme.secondary))
+            Box(modifier = Modifier.size(1.dp).background(
+                MaterialTheme.colorScheme.contentColorFor(MaterialTheme.colorScheme.secondary))
+            )
+            Box(modifier = Modifier.size(1.dp).background(MaterialTheme.colorScheme.tertiary))
+            Box(modifier = Modifier.size(1.dp).background(
+                MaterialTheme.colorScheme.contentColorFor(MaterialTheme.colorScheme.tertiary))
+            )
+            Box(modifier = Modifier.size(1.dp).background(MaterialTheme.colorScheme.error))
+            Box(modifier = Modifier.size(1.dp).background(
+                MaterialTheme.colorScheme.contentColorFor(MaterialTheme.colorScheme.error))
+            )
+            Box(modifier = Modifier.size(1.dp).background(
+                MaterialTheme.colorScheme.surfaceContainerLowest)
+            )
+            Box(modifier = Modifier.size(1.dp).background(
+                MaterialTheme.colorScheme.contentColorFor(
+                    MaterialTheme.colorScheme.surfaceContainerLowest)
+                )
+            )
+        }
+    }
+
+    @Composable
+    override fun ContentWrappers(content: @Composable () -> Unit) {
+        MaterialTheme {
+            content()
+        }
+    }
+}
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index d665b1b..82b469d 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -29,13 +29,17 @@
   }
 
   public final class AppBarKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void BottomAppBar(optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.BottomAppBarScrollBehavior? scrollBehavior, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void BottomAppBar(optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void BottomAppBar(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void BottomAppBar(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.BottomAppBarScrollBehavior? scrollBehavior);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.BottomAppBarState BottomAppBarState(float initialHeightOffsetLimit, float initialHeightOffset, float initialContentOffset);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void CenterAlignedTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void LargeTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void MediumTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
     method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SmallTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.BottomAppBarState rememberBottomAppBarState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TopAppBarState rememberTopAppBarState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
   }
 
@@ -66,6 +70,7 @@
   }
 
   public final class BottomAppBarDefaults {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.BottomAppBarScrollBehavior exitAlwaysScrollBehavior(optional androidx.compose.material3.BottomAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec);
     method @androidx.compose.runtime.Composable public long getBottomAppBarFabColor();
     method @androidx.compose.runtime.Composable public long getContainerColor();
     method public float getContainerElevation();
@@ -79,6 +84,39 @@
     field public static final androidx.compose.material3.BottomAppBarDefaults INSTANCE;
   }
 
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface BottomAppBarScrollBehavior {
+    method public androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? getFlingAnimationSpec();
+    method public androidx.compose.ui.input.nestedscroll.NestedScrollConnection getNestedScrollConnection();
+    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float>? getSnapAnimationSpec();
+    method public androidx.compose.material3.BottomAppBarState getState();
+    method public boolean isPinned();
+    property public abstract androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec;
+    property public abstract boolean isPinned;
+    property public abstract androidx.compose.ui.input.nestedscroll.NestedScrollConnection nestedScrollConnection;
+    property public abstract androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec;
+    property public abstract androidx.compose.material3.BottomAppBarState state;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface BottomAppBarState {
+    method public float getCollapsedFraction();
+    method public float getContentOffset();
+    method public float getHeightOffset();
+    method public float getHeightOffsetLimit();
+    method public void setContentOffset(float);
+    method public void setHeightOffset(float);
+    method public void setHeightOffsetLimit(float);
+    property public abstract float collapsedFraction;
+    property public abstract float contentOffset;
+    property public abstract float heightOffset;
+    property public abstract float heightOffsetLimit;
+    field public static final androidx.compose.material3.BottomAppBarState.Companion Companion;
+  }
+
+  public static final class BottomAppBarState.Companion {
+    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.BottomAppBarState,?> getSaver();
+    property public final androidx.compose.runtime.saveable.Saver<androidx.compose.material3.BottomAppBarState,?> Saver;
+  }
+
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class BottomSheetDefaults {
     method @androidx.compose.runtime.Composable public void DragHandle(optional androidx.compose.ui.Modifier modifier, optional float width, optional float height, optional androidx.compose.ui.graphics.Shape shape, optional long color);
     method @androidx.compose.runtime.Composable public long getContainerColor();
@@ -677,9 +715,11 @@
   public static final class FabPosition.Companion {
     method public int getCenter();
     method public int getEnd();
+    method public int getEndOverlay();
     method public int getStart();
     property public final int Center;
     property public final int End;
+    property public final int EndOverlay;
     property public final int Start;
   }
 
@@ -829,6 +869,10 @@
     property @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumTouchTargetEnforcement;
   }
 
+  public final class LabelKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Label(kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional boolean isPersistent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
   @androidx.compose.runtime.Immutable public final class ListItemColors {
     ctor public ListItemColors(long containerColor, long headlineColor, long leadingIconColor, long overlineColor, long supportingTextColor, long trailingIconColor, long disabledHeadlineColor, long disabledLeadingIconColor, long disabledTrailingIconColor);
     method public long getContainerColor();
@@ -1130,6 +1174,9 @@
 
   public final class ScaffoldKt {
     method @androidx.compose.runtime.Composable public static void Scaffold(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> topBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> bottomBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> snackbarHost, optional kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional int floatingActionButtonPosition, optional long containerColor, optional long contentColor, optional androidx.compose.foundation.layout.WindowInsets contentWindowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static boolean getScaffoldSubcomposeInMeasureFix();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static void setScaffoldSubcomposeInMeasureFix(boolean);
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final boolean ScaffoldSubcomposeInMeasureFix;
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class SearchBarColors {
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index d665b1b..82b469d 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -29,13 +29,17 @@
   }
 
   public final class AppBarKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void BottomAppBar(optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.BottomAppBarScrollBehavior? scrollBehavior, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void BottomAppBar(optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void BottomAppBar(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void BottomAppBar(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? floatingActionButton, optional long containerColor, optional long contentColor, optional float tonalElevation, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.BottomAppBarScrollBehavior? scrollBehavior);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static androidx.compose.material3.BottomAppBarState BottomAppBarState(float initialHeightOffsetLimit, float initialHeightOffset, float initialContentOffset);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void CenterAlignedTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void LargeTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void MediumTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
     method @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SmallTopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TopAppBar(kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> navigationIcon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> actions, optional androidx.compose.foundation.layout.WindowInsets windowInsets, optional androidx.compose.material3.TopAppBarColors colors, optional androidx.compose.material3.TopAppBarScrollBehavior? scrollBehavior);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.BottomAppBarState rememberBottomAppBarState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
     method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TopAppBarState rememberTopAppBarState(optional float initialHeightOffsetLimit, optional float initialHeightOffset, optional float initialContentOffset);
   }
 
@@ -66,6 +70,7 @@
   }
 
   public final class BottomAppBarDefaults {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.BottomAppBarScrollBehavior exitAlwaysScrollBehavior(optional androidx.compose.material3.BottomAppBarState state, optional kotlin.jvm.functions.Function0<java.lang.Boolean> canScroll, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec);
     method @androidx.compose.runtime.Composable public long getBottomAppBarFabColor();
     method @androidx.compose.runtime.Composable public long getContainerColor();
     method public float getContainerElevation();
@@ -79,6 +84,39 @@
     field public static final androidx.compose.material3.BottomAppBarDefaults INSTANCE;
   }
 
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public interface BottomAppBarScrollBehavior {
+    method public androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? getFlingAnimationSpec();
+    method public androidx.compose.ui.input.nestedscroll.NestedScrollConnection getNestedScrollConnection();
+    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float>? getSnapAnimationSpec();
+    method public androidx.compose.material3.BottomAppBarState getState();
+    method public boolean isPinned();
+    property public abstract androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float>? flingAnimationSpec;
+    property public abstract boolean isPinned;
+    property public abstract androidx.compose.ui.input.nestedscroll.NestedScrollConnection nestedScrollConnection;
+    property public abstract androidx.compose.animation.core.AnimationSpec<java.lang.Float>? snapAnimationSpec;
+    property public abstract androidx.compose.material3.BottomAppBarState state;
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface BottomAppBarState {
+    method public float getCollapsedFraction();
+    method public float getContentOffset();
+    method public float getHeightOffset();
+    method public float getHeightOffsetLimit();
+    method public void setContentOffset(float);
+    method public void setHeightOffset(float);
+    method public void setHeightOffsetLimit(float);
+    property public abstract float collapsedFraction;
+    property public abstract float contentOffset;
+    property public abstract float heightOffset;
+    property public abstract float heightOffsetLimit;
+    field public static final androidx.compose.material3.BottomAppBarState.Companion Companion;
+  }
+
+  public static final class BottomAppBarState.Companion {
+    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.BottomAppBarState,?> getSaver();
+    property public final androidx.compose.runtime.saveable.Saver<androidx.compose.material3.BottomAppBarState,?> Saver;
+  }
+
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class BottomSheetDefaults {
     method @androidx.compose.runtime.Composable public void DragHandle(optional androidx.compose.ui.Modifier modifier, optional float width, optional float height, optional androidx.compose.ui.graphics.Shape shape, optional long color);
     method @androidx.compose.runtime.Composable public long getContainerColor();
@@ -677,9 +715,11 @@
   public static final class FabPosition.Companion {
     method public int getCenter();
     method public int getEnd();
+    method public int getEndOverlay();
     method public int getStart();
     property public final int Center;
     property public final int End;
+    property public final int EndOverlay;
     property public final int Start;
   }
 
@@ -829,6 +869,10 @@
     property @Deprecated @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumTouchTargetEnforcement;
   }
 
+  public final class LabelKt {
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Label(kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional boolean isPersistent, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
   @androidx.compose.runtime.Immutable public final class ListItemColors {
     ctor public ListItemColors(long containerColor, long headlineColor, long leadingIconColor, long overlineColor, long supportingTextColor, long trailingIconColor, long disabledHeadlineColor, long disabledLeadingIconColor, long disabledTrailingIconColor);
     method public long getContainerColor();
@@ -1130,6 +1174,9 @@
 
   public final class ScaffoldKt {
     method @androidx.compose.runtime.Composable public static void Scaffold(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> topBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> bottomBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> snackbarHost, optional kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional int floatingActionButtonPosition, optional long containerColor, optional long contentColor, optional androidx.compose.foundation.layout.WindowInsets contentWindowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static boolean getScaffoldSubcomposeInMeasureFix();
+    method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static void setScaffoldSubcomposeInMeasureFix(boolean);
+    property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final boolean ScaffoldSubcomposeInMeasureFix;
   }
 
   @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class SearchBarColors {
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
index c6d720e..2e8c6a2 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
@@ -57,6 +57,7 @@
 import androidx.compose.material3.samples.ElevatedFilterChipSample
 import androidx.compose.material3.samples.ElevatedSuggestionChipSample
 import androidx.compose.material3.samples.EnterAlwaysTopAppBar
+import androidx.compose.material3.samples.ExitAlwaysBottomAppBar
 import androidx.compose.material3.samples.ExitUntilCollapsedLargeTopAppBar
 import androidx.compose.material3.samples.ExitUntilCollapsedMediumTopAppBar
 import androidx.compose.material3.samples.ExposedDropdownMenuSample
@@ -465,7 +466,12 @@
         name = ::BottomAppBarWithFAB.name,
         description = BottomAppBarsExampleDescription,
         sourceUrl = BottomAppBarsExampleSourceUrl,
-    ) { BottomAppBarWithFAB() }
+    ) { BottomAppBarWithFAB() },
+    Example(
+        name = ::ExitAlwaysBottomAppBar.name,
+        description = BottomAppBarsExampleDescription,
+        sourceUrl = BottomAppBarsExampleSourceUrl,
+    ) { ExitAlwaysBottomAppBar() }
 )
 
 private const val TopAppBarExampleDescription = "Top app bar examples"
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/AppBarSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/AppBarSamples.kt
index 6cbc0aa..a6538f0 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/AppBarSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/AppBarSamples.kt
@@ -19,6 +19,7 @@
 import androidx.annotation.Sampled
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.material.icons.Icons
@@ -31,6 +32,7 @@
 import androidx.compose.material3.BottomAppBarDefaults
 import androidx.compose.material3.CenterAlignedTopAppBar
 import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FabPosition
 import androidx.compose.material3.FloatingActionButton
 import androidx.compose.material3.FloatingActionButtonDefaults
 import androidx.compose.material3.Icon
@@ -418,11 +420,13 @@
 @Sampled
 @Composable
 fun SimpleBottomAppBar() {
-    BottomAppBar {
-        IconButton(onClick = { /* doSomething() */ }) {
-            Icon(Icons.Filled.Menu, contentDescription = "Localized description")
+    BottomAppBar(
+        actions = {
+            IconButton(onClick = { /* doSomething() */ }) {
+                Icon(Icons.Filled.Menu, contentDescription = "Localized description")
+            }
         }
-    }
+    )
 }
 
 @Preview
@@ -452,3 +456,59 @@
         }
     )
 }
+
+/**
+ * A sample for a [BottomAppBar] that collapses when the content is scrolled up, and
+ * appears when the content scrolled down.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview
+@Sampled
+@Composable
+fun ExitAlwaysBottomAppBar() {
+    val scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
+    Scaffold(
+        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+        bottomBar = {
+            BottomAppBar(
+                actions = {
+                    IconButton(onClick = { /* doSomething() */ }) {
+                        Icon(Icons.Filled.Check, contentDescription = "Localized description")
+                    }
+                    IconButton(onClick = { /* doSomething() */ }) {
+                        Icon(Icons.Filled.Edit, contentDescription = "Localized description")
+                    }
+                },
+                scrollBehavior = scrollBehavior
+            )
+        },
+        floatingActionButton = {
+            FloatingActionButton(
+                modifier = Modifier.offset(y = 4.dp),
+                onClick = { /* do something */ },
+                containerColor = BottomAppBarDefaults.bottomAppBarFabColor,
+                elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation()
+            ) {
+                Icon(Icons.Filled.Add, "Localized description")
+            }
+        },
+        floatingActionButtonPosition = FabPosition.EndOverlay,
+        content = { innerPadding ->
+            LazyColumn(
+                contentPadding = innerPadding,
+                verticalArrangement = Arrangement.spacedBy(8.dp)
+            ) {
+                val list = (0..75).map { it.toString() }
+                items(count = list.size) {
+                    Text(
+                        text = list[it],
+                        style = MaterialTheme.typography.bodyLarge,
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .padding(horizontal = 16.dp)
+                    )
+                }
+            }
+        }
+    )
+}
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
index 77929ef..2528da4 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
@@ -19,12 +19,16 @@
 import androidx.annotation.Sampled
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentWidth
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
+import androidx.compose.material3.Label
+import androidx.compose.material3.PlainTooltip
 import androidx.compose.material3.RangeSlider
 import androidx.compose.material3.RangeSliderState
 import androidx.compose.material3.Slider
@@ -41,6 +45,8 @@
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import kotlin.math.roundToInt
 
 @Preview
 @Sampled
@@ -56,7 +62,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Preview
 @Sampled
 @Composable
@@ -84,25 +89,40 @@
 @Composable
 fun SliderWithCustomThumbSample() {
     var sliderPosition by remember { mutableStateOf(0f) }
+    val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
     Column {
-        Text(text = sliderPosition.toString())
         Slider(
             modifier = Modifier.semantics { contentDescription = "Localized Description" },
             value = sliderPosition,
             onValueChange = { sliderPosition = it },
-            valueRange = 0f..5f,
-            steps = 10,
+            valueRange = 0f..100f,
+            interactionSource = interactionSource,
             onValueChangeFinished = {
                 // launch some business logic update with the state you hold
                 // viewModel.updateSelectedSliderValue(sliderPosition)
             },
             thumb = {
-                Icon(
-                    imageVector = Icons.Filled.Favorite,
-                    contentDescription = null,
-                    modifier = Modifier.size(ButtonDefaults.IconSize),
-                    tint = Color.Red
-                )
+                Label(
+                    label = {
+                        PlainTooltip(
+                            modifier = Modifier
+                                .requiredSize(45.dp, 25.dp)
+                                .wrapContentWidth()
+                        ) {
+                            val roundedEnd =
+                                (sliderPosition * 100.0).roundToInt() / 100.0
+                            Text(roundedEnd.toString())
+                        }
+                    },
+                    interactionSource = interactionSource
+                ) {
+                    Icon(
+                        imageVector = Icons.Filled.Favorite,
+                        contentDescription = null,
+                        modifier = Modifier.size(ButtonDefaults.IconSize),
+                        tint = Color.Red
+                    )
+                }
             }
         )
     }
@@ -221,23 +241,52 @@
     )
     val endThumbColors = SliderDefaults.colors(thumbColor = Color.Green)
     Column {
-        Text(text = (rangeSliderState.activeRangeStart..rangeSliderState.activeRangeEnd).toString())
         RangeSlider(
             state = rangeSliderState,
             modifier = Modifier.semantics { contentDescription = "Localized Description" },
             startInteractionSource = startInteractionSource,
             endInteractionSource = endInteractionSource,
             startThumb = {
-                SliderDefaults.Thumb(
-                    interactionSource = startInteractionSource,
-                    colors = startThumbAndTrackColors
-                )
+                Label(
+                    label = {
+                        PlainTooltip(
+                            modifier = Modifier
+                                .requiredSize(45.dp, 25.dp)
+                                .wrapContentWidth()
+                        ) {
+                            val roundedStart =
+                                (rangeSliderState.activeRangeStart * 100.0).roundToInt() / 100.0
+                            Text(roundedStart.toString())
+                        }
+                    },
+                    interactionSource = startInteractionSource
+                ) {
+                    SliderDefaults.Thumb(
+                        interactionSource = startInteractionSource,
+                        colors = startThumbAndTrackColors
+                    )
+                }
             },
             endThumb = {
-                SliderDefaults.Thumb(
-                    interactionSource = endInteractionSource,
-                    colors = endThumbColors
-                )
+                Label(
+                    label = {
+                        PlainTooltip(
+                            modifier = Modifier
+                                .requiredSize(45.dp, 25.dp)
+                                .wrapContentWidth()
+                        ) {
+                            val roundedEnd =
+                                (rangeSliderState.activeRangeEnd * 100.0).roundToInt() / 100.0
+                            Text(roundedEnd.toString())
+                        }
+                    },
+                    interactionSource = endInteractionSource
+                ) {
+                    SliderDefaults.Thumb(
+                        interactionSource = endInteractionSource,
+                        colors = endThumbColors
+                    )
+                }
             },
             track = { rangeSliderState ->
                 SliderDefaults.Track(
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarScreenshotTest.kt
index ddbb438..7b8d1a5 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarScreenshotTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarScreenshotTest.kt
@@ -24,6 +24,7 @@
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material.icons.filled.Menu
+import androidx.compose.material3.BottomAppBarDefaults.bottomAppBarFabColor
 import androidx.compose.material3.TopAppBarDefaults.enterAlwaysScrollBehavior
 import androidx.compose.material3.tokens.TopAppBarSmallTokens
 import androidx.compose.testutils.assertAgainstGolden
@@ -361,7 +362,7 @@
                     floatingActionButton = {
                         FloatingActionButton(
                             onClick = { /* do something */ },
-                            containerColor = BottomAppBarDefaults.bottomAppBarFabColor,
+                            containerColor = bottomAppBarFabColor,
                             elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation()
                         ) {
                             Icon(Icons.Filled.Add, "Localized description")
@@ -393,7 +394,7 @@
                     floatingActionButton = {
                         FloatingActionButton(
                             onClick = { /* do something */ },
-                            containerColor = BottomAppBarDefaults.bottomAppBarFabColor,
+                            containerColor = bottomAppBarFabColor,
                             elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation()
                         ) {
                             Icon(Icons.Filled.Add, "Localized description")
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
index ef6ffe4..a913ceb 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
@@ -23,6 +23,7 @@
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyListState
@@ -1183,6 +1184,105 @@
             .assertTopPositionInRootIsEqualTo(12.dp)
     }
 
+    @Test
+    fun bottomAppBar_exitAlways_scaffoldWithFAB_default_positioning() {
+        rule.setMaterialContent(lightColorScheme()) {
+            val scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
+            Scaffold(
+                modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+                bottomBar = {
+                    BottomAppBar(
+                        modifier = Modifier.testTag(BottomAppBarTestTag),
+                        scrollBehavior = scrollBehavior
+                    ) {}
+                },
+                floatingActionButton = {
+                    FloatingActionButton(
+                        modifier = Modifier
+                            .testTag("FAB")
+                            .offset(y = 4.dp),
+                        onClick = { /* do something */ },
+                    ) {}
+                },
+                floatingActionButtonPosition = FabPosition.EndOverlay
+            ) {}
+        }
+
+        val appBarBounds = rule.onNodeWithTag(BottomAppBarTestTag).getUnclippedBoundsInRoot()
+        val fabBounds = rule.onNodeWithTag("FAB").getUnclippedBoundsInRoot()
+        rule.onNodeWithTag("FAB")
+            // FAB should be 16.dp from the end
+            .assertLeftPositionInRootIsEqualTo(appBarBounds.width - 16.dp - fabBounds.width)
+            // FAB should be 12.dp from the bottom
+            .assertTopPositionInRootIsEqualTo(rule.rootHeight() - 12.dp - fabBounds.height)
+    }
+
+    @Test
+    fun bottomAppBar_exitAlways_scaffoldWithFAB_scrolled_positioning() {
+        lateinit var scrollBehavior: BottomAppBarScrollBehavior
+        val scrollHeightOffsetDp = 20.dp
+        var scrollHeightOffsetPx = 0f
+
+        rule.setMaterialContent(lightColorScheme()) {
+            scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
+            scrollHeightOffsetPx = with(LocalDensity.current) { scrollHeightOffsetDp.toPx() }
+            Scaffold(
+                modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+                bottomBar = {
+                    BottomAppBar(
+                        modifier = Modifier.testTag(BottomAppBarTestTag),
+                        scrollBehavior = scrollBehavior
+                    ) {}
+                },
+                floatingActionButton = {
+                    FloatingActionButton(
+                        modifier = Modifier
+                            .testTag("FAB")
+                            .offset(y = 4.dp),
+                        onClick = { /* do something */ },
+                    ) {}
+                },
+                floatingActionButtonPosition = FabPosition.EndOverlay
+            ) {}
+        }
+
+        // Simulate scrolled content.
+        rule.runOnIdle {
+            scrollBehavior.state.heightOffset = -scrollHeightOffsetPx
+            scrollBehavior.state.contentOffset = -scrollHeightOffsetPx
+        }
+        rule.waitForIdle()
+        rule.onNodeWithTag(BottomAppBarTestTag)
+            .assertHeightIsEqualTo(BottomAppBarTokens.ContainerHeight - scrollHeightOffsetDp)
+
+        val appBarBounds = rule.onNodeWithTag(BottomAppBarTestTag).getUnclippedBoundsInRoot()
+        val fabBounds = rule.onNodeWithTag("FAB").getUnclippedBoundsInRoot()
+        rule.onNodeWithTag("FAB")
+            // FAB should be 16.dp from the end
+            .assertLeftPositionInRootIsEqualTo(appBarBounds.width - 16.dp - fabBounds.width)
+            // FAB should be 12.dp from the bottom
+            .assertTopPositionInRootIsEqualTo(rule.rootHeight() - 12.dp - fabBounds.height)
+    }
+
+    @Test
+    fun bottomAppBar_exitAlways_allowHorizontalScroll() {
+        lateinit var state: LazyListState
+        rule.setMaterialContent(lightColorScheme()) {
+            state = rememberLazyListState()
+            MultiPageContent(BottomAppBarDefaults.exitAlwaysScrollBehavior(), state)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+        }
+    }
+
     @OptIn(ExperimentalMaterial3Api::class)
     @Composable
     private fun MultiPageContent(scrollBehavior: TopAppBarScrollBehavior, state: LazyListState) {
@@ -1218,6 +1318,39 @@
         }
     }
 
+    @Composable
+    private fun MultiPageContent(scrollBehavior: BottomAppBarScrollBehavior, state: LazyListState) {
+        Scaffold(
+            modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+            bottomBar = {
+                BottomAppBar(
+                    modifier = Modifier.testTag(BottomAppBarTestTag),
+                    scrollBehavior = scrollBehavior
+                ) {}
+            }
+        ) { contentPadding ->
+            LazyRow(
+                Modifier
+                    .fillMaxSize()
+                    .testTag(LazyListTag), state
+            ) {
+                items(2) { page ->
+                    LazyColumn(
+                        modifier = Modifier.fillParentMaxSize(),
+                        contentPadding = contentPadding
+                    ) {
+                        items(50) {
+                            Text(
+                                modifier = Modifier.fillParentMaxWidth(),
+                                text = "Item #$page x $it"
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * Checks the app bar's components positioning when it's a [TopAppBar], a
      * [CenterAlignedTopAppBar], or a larger app bar that is scrolled up and collapsed into a small
@@ -1583,7 +1716,8 @@
         (TopAppBarSmallTokens.ContainerHeight - FakeIconSize) / 2
 
     private val LazyListTag = "lazyList"
-    private val TopAppBarTestTag = "bar"
+    private val TopAppBarTestTag = "topAppBar"
+    private val BottomAppBarTestTag = "bottomAppBar"
     private val NavigationIconTestTag = "navigationIcon"
     private val TitleTestTag = "title"
     private val ActionsTestTag = "actions"
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ExposedDropdownMenuTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ExposedDropdownMenuTest.kt
index bbae528..347ce70 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ExposedDropdownMenuTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ExposedDropdownMenuTest.kt
@@ -525,6 +525,7 @@
         // Should not have crashed.
     }
 
+    @Ignore("b/297059209")
     @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun edm_withScrolledContent() {
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
index 9dfb619..925e620 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/FloatingActionButtonTest.kt
@@ -552,7 +552,7 @@
         val pressedElevation = 2.dp
         val hoveredElevation = 3.dp
         val focusedElevation = 4.dp
-        lateinit var tonalElevation: State<Dp>
+        var tonalElevation: Dp = Dp.Unspecified
         lateinit var shadowElevation: State<Dp>
 
         rule.setMaterialContent(lightColorScheme()) {
@@ -563,12 +563,12 @@
                 focusedElevation = focusedElevation
             )
 
-            tonalElevation = fabElevation.tonalElevation(interactionSource)
+            tonalElevation = fabElevation.tonalElevation()
             shadowElevation = fabElevation.shadowElevation(interactionSource)
         }
 
         rule.runOnIdle {
-            assertThat(tonalElevation.value).isEqualTo(defaultElevation)
+            assertThat(tonalElevation).isEqualTo(defaultElevation)
             assertThat(shadowElevation.value).isEqualTo(defaultElevation)
         }
 
@@ -577,7 +577,7 @@
         }
 
         rule.runOnIdle {
-            assertThat(tonalElevation.value).isEqualTo(pressedElevation)
+            assertThat(tonalElevation).isEqualTo(defaultElevation)
             assertThat(shadowElevation.value).isEqualTo(pressedElevation)
         }
     }
@@ -589,7 +589,7 @@
         val pressedElevation = 2.dp
         val hoveredElevation = 3.dp
         val focusedElevation = 4.dp
-        lateinit var tonalElevation: State<Dp>
+        var tonalElevation: Dp = Dp.Unspecified
         lateinit var shadowElevation: State<Dp>
 
         rule.setMaterialContent(lightColorScheme()) {
@@ -600,12 +600,12 @@
                 focusedElevation = focusedElevation
             )
 
-            tonalElevation = fabElevation.tonalElevation(interactionSource)
+            tonalElevation = fabElevation.tonalElevation()
             shadowElevation = fabElevation.shadowElevation(interactionSource)
         }
 
         rule.runOnIdle {
-            assertThat(tonalElevation.value).isEqualTo(defaultElevation)
+            assertThat(tonalElevation).isEqualTo(defaultElevation)
             assertThat(shadowElevation.value).isEqualTo(defaultElevation)
         }
 
@@ -614,7 +614,7 @@
         }
 
         rule.runOnIdle {
-            assertThat(tonalElevation.value).isEqualTo(5.dp)
+            assertThat(tonalElevation).isEqualTo(5.dp)
             assertThat(shadowElevation.value).isEqualTo(5.dp)
         }
     }
@@ -626,7 +626,7 @@
         var pressedElevation by mutableStateOf(2.dp)
         val hoveredElevation = 3.dp
         val focusedElevation = 4.dp
-        lateinit var tonalElevation: State<Dp>
+        var tonalElevation: Dp = Dp.Unspecified
         lateinit var shadowElevation: State<Dp>
 
         rule.setMaterialContent(lightColorScheme()) {
@@ -637,12 +637,12 @@
                 focusedElevation = focusedElevation
             )
 
-            tonalElevation = fabElevation.tonalElevation(interactionSource)
+            tonalElevation = fabElevation.tonalElevation()
             shadowElevation = fabElevation.shadowElevation(interactionSource)
         }
 
         rule.runOnIdle {
-            assertThat(tonalElevation.value).isEqualTo(defaultElevation)
+            assertThat(tonalElevation).isEqualTo(defaultElevation)
             assertThat(shadowElevation.value).isEqualTo(defaultElevation)
         }
 
@@ -651,7 +651,7 @@
         }
 
         rule.runOnIdle {
-            assertThat(tonalElevation.value).isEqualTo(pressedElevation)
+            assertThat(tonalElevation).isEqualTo(defaultElevation)
             assertThat(shadowElevation.value).isEqualTo(pressedElevation)
         }
 
@@ -661,7 +661,7 @@
 
         // We are still pressed, so we should now show the updated value for the pressed state
         rule.runOnIdle {
-            assertThat(tonalElevation.value).isEqualTo(5.dp)
+            assertThat(tonalElevation).isEqualTo(defaultElevation)
             assertThat(shadowElevation.value).isEqualTo(5.dp)
         }
     }
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
index 85a2ddc..859ae81 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
@@ -48,7 +48,6 @@
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
@@ -144,15 +143,49 @@
     }
 
     @Test
+    fun modalBottomSheet_isDismissedOnSwipeDown() {
+        var showBottomSheet by mutableStateOf(true)
+        val sheetState = SheetState(skipPartiallyExpanded = false, density = rule.density)
+
+        rule.setContent {
+            val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
+                WindowInsets(0) else BottomSheetDefaults.windowInsets
+
+            if (showBottomSheet) {
+                ModalBottomSheet(
+                    sheetState = sheetState,
+                    onDismissRequest = { showBottomSheet = false },
+                    windowInsets = windowInsets
+                ) {
+                    Box(
+                        Modifier
+                            .size(sheetHeight)
+                            .testTag(sheetTag)
+                    )
+                }
+            }
+        }
+
+        assertThat(sheetState.isVisible).isTrue()
+
+        // Swipe Down
+        rule.onNodeWithTag(sheetTag).performTouchInput {
+            swipeDown()
+        }
+        rule.waitForIdle()
+
+        // Bottom sheet should not exist
+        rule.onNodeWithTag(sheetTag).assertDoesNotExist()
+    }
+
+    @Test
     fun modalBottomSheet_fillsScreenWidth() {
         var boxWidth = 0
         var screenWidth by mutableStateOf(0)
 
         rule.setContent {
             val context = LocalContext.current
-            val density = LocalDensity.current
-            val resScreenWidth = context.resources.configuration.screenWidthDp
-            with(density) { screenWidth = resScreenWidth.dp.roundToPx() }
+            screenWidth = context.resources.displayMetrics.widthPixels
             val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
                 WindowInsets(0) else BottomSheetDefaults.windowInsets
 
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ScaffoldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
index 5ab689e..ab6f9b9 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
@@ -32,6 +32,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.LookaheadScope
 import androidx.compose.ui.layout.SubcomposeLayout
@@ -657,6 +658,72 @@
         assertWithMessage("Expected placeCount to be >= 1").that(onPlaceCount).isAtLeast(1)
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun scaffold_subcomposeInMeasureFix_enabled_measuresChildrenInMeasurement() {
+        ScaffoldSubcomposeInMeasureFix = true
+        var size: IntSize? = null
+        var measured = false
+        rule.setContent {
+            Layout(
+                content = {
+                    Scaffold(
+                        content = {
+                            Box(Modifier.onSizeChanged { size = it })
+                        }
+                    )
+                }
+            ) { measurables, constraints ->
+                measurables.map { it.measure(constraints) }
+                measured = true
+                layout(0, 0) {
+                    // Empty measurement since we only care about placement
+                }
+            }
+        }
+
+        assertWithMessage("Measure should have been executed")
+            .that(measured).isTrue()
+        assertWithMessage("Expected size to be initialized")
+            .that(size).isNotNull()
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun scaffold_subcomposeInMeasureFix_disabled_measuresChildrenInPlacement() {
+        ScaffoldSubcomposeInMeasureFix = false
+        var size: IntSize? = null
+        var measured = false
+        var placed = false
+        rule.setContent {
+            Layout(
+                content = {
+                    Scaffold(
+                        content = {
+                            Box(Modifier.onSizeChanged { size = it })
+                        }
+                    )
+                }
+            ) { measurables, constraints ->
+                val placeables = measurables.map { it.measure(constraints) }
+                measured = true
+                assertWithMessage("Expected size to not be initialized in placement")
+                    .that(size).isNull()
+                layout(constraints.maxWidth, constraints.maxHeight) {
+                    placeables.forEach { it.place(0, 0) }
+                    placed = true
+                }
+            }
+        }
+
+        assertWithMessage("Measure should have been executed")
+            .that(measured).isTrue()
+        assertWithMessage("Placement should have been executed")
+            .that(placed).isTrue()
+        assertWithMessage("Expected size to be initialized")
+            .that(size).isNotNull()
+    }
+
     private fun assertDpIsWithinThreshold(actual: Dp, expected: Dp, threshold: Dp) {
         assertThat(actual.value).isWithin(threshold.value).of(expected.value)
     }
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
index 36514dd..883e39b 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderScreenshotTest.kt
@@ -216,8 +216,8 @@
         assertSliderAgainstGolden("slider_customColors_disabled")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
-    @ExperimentalMaterial3Api
     fun rangeSliderTest_middle_steps_disabled() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
@@ -232,8 +232,8 @@
         assertSliderAgainstGolden("rangeSlider_middle_steps_disabled")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
-    @ExperimentalMaterial3Api
     fun rangeSliderTest_middle_steps_enabled() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
@@ -251,8 +251,8 @@
         assertSliderAgainstGolden("rangeSlider_middle_steps_enabled")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
-    @ExperimentalMaterial3Api
     fun rangeSliderTest_middle_steps_dark_enabled() {
         rule.setMaterialContent(darkColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
@@ -270,8 +270,8 @@
         assertSliderAgainstGolden("rangeSlider_middle_steps_dark_enabled")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
-    @ExperimentalMaterial3Api
     fun rangeSliderTest_middle_steps_dark_disabled() {
         rule.setMaterialContent(darkColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
@@ -286,8 +286,8 @@
         assertSliderAgainstGolden("rangeSlider_middle_steps_dark_disabled")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
-    @ExperimentalMaterial3Api
     fun rangeSliderTest_overlapingThumbs() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
@@ -301,8 +301,8 @@
         assertSliderAgainstGolden("rangeSlider_overlapingThumbs")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
-    @ExperimentalMaterial3Api
     fun rangeSliderTest_fullRange() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
@@ -316,8 +316,8 @@
         assertSliderAgainstGolden("rangeSlider_fullRange")
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
     @Test
-    @ExperimentalMaterial3Api
     fun rangeSliderTest_steps_customColors() {
         rule.setMaterialContent(lightColorScheme()) {
             Box(wrap.testTag(wrapperTestTag)) {
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DynamicTonalPalette.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DynamicTonalPalette.kt
index 1fbf2f9..bafbf77 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DynamicTonalPalette.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DynamicTonalPalette.kt
@@ -318,9 +318,9 @@
     onTertiary = tonalPalette.tertiary100,
     tertiaryContainer = tonalPalette.tertiary90,
     onTertiaryContainer = tonalPalette.tertiary10,
-    background = tonalPalette.neutral99,
+    background = tonalPalette.neutral98,
     onBackground = tonalPalette.neutral10,
-    surface = tonalPalette.neutral99,
+    surface = tonalPalette.neutral98,
     onSurface = tonalPalette.neutral10,
     surfaceVariant = tonalPalette.neutralVariant90,
     onSurfaceVariant = tonalPalette.neutralVariant30,
@@ -354,9 +354,9 @@
     onTertiary = tonalPalette.tertiary20,
     tertiaryContainer = tonalPalette.tertiary30,
     onTertiaryContainer = tonalPalette.tertiary90,
-    background = tonalPalette.neutralVariant10,
+    background = tonalPalette.neutralVariant6,
     onBackground = tonalPalette.neutralVariant90,
-    surface = tonalPalette.neutralVariant10,
+    surface = tonalPalette.neutralVariant6,
     onSurface = tonalPalette.neutralVariant90,
     surfaceVariant = tonalPalette.neutralVariant30,
     onSurfaceVariant = tonalPalette.neutralVariant80,
@@ -390,9 +390,9 @@
     onTertiary = tonalPalette.tertiary20,
     tertiaryContainer = tonalPalette.tertiary30,
     onTertiaryContainer = tonalPalette.tertiary90,
-    background = tonalPalette.neutral10,
+    background = tonalPalette.neutral6,
     onBackground = tonalPalette.neutral90,
-    surface = tonalPalette.neutral10,
+    surface = tonalPalette.neutral6,
     onSurface = tonalPalette.neutral90,
     surfaceVariant = tonalPalette.neutralVariant30,
     onSurfaceVariant = tonalPalette.neutralVariant80,
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
index 85e41b3..a053929a 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
@@ -28,6 +28,7 @@
 import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.gestures.draggable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxWithConstraints
 import androidx.compose.foundation.layout.Column
@@ -87,7 +88,6 @@
 import androidx.savedstate.setViewTreeSavedStateRegistryOwner
 import java.util.UUID
 import kotlin.math.max
-import kotlin.math.roundToInt
 import kotlinx.coroutines.launch
 
 /**
@@ -198,10 +198,12 @@
                             )
                         }
                     )
-                    .anchoredDraggable(
-                        state = sheetState.anchoredDraggableState,
+                    .draggable(
+                        state = sheetState.anchoredDraggableState.draggableState,
                         orientation = Orientation.Vertical,
-                        enabled = sheetState.isVisible
+                        enabled = sheetState.isVisible,
+                        startDragImmediately = sheetState.anchoredDraggableState.isAnimationRunning,
+                        onDragStopped = { settleToDismiss(it) }
                     )
                     .modalBottomSheetAnchors(
                         sheetState = sheetState,
@@ -414,10 +416,7 @@
         composeView.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
 
     private val displayWidth: Int
-        get() {
-            val density = context.resources.displayMetrics.density
-            return (context.resources.configuration.screenWidthDp * density).roundToInt()
-        }
+        get() = context.resources.displayMetrics.widthPixels
 
     private val params: WindowManager.LayoutParams =
         WindowManager.LayoutParams().apply {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
index cd79a42..7d8c9f6 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
@@ -73,6 +73,7 @@
 import androidx.compose.ui.layout.AlignmentLine
 import androidx.compose.ui.layout.LastBaseline
 import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.layout
 import androidx.compose.ui.layout.layoutId
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.semantics.clearAndSetSemantics
@@ -391,6 +392,7 @@
  * @param contentPadding the padding applied to the content of this BottomAppBar
  * @param windowInsets a window insets that app bar will respect.
  */
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun BottomAppBar(
     actions: @Composable RowScope.() -> Unit,
@@ -402,12 +404,76 @@
     contentPadding: PaddingValues = BottomAppBarDefaults.ContentPadding,
     windowInsets: WindowInsets = BottomAppBarDefaults.windowInsets,
 ) = BottomAppBar(
+    actions = actions,
+    modifier = modifier,
+    floatingActionButton = floatingActionButton,
+    containerColor = containerColor,
+    contentColor = contentColor,
+    tonalElevation = tonalElevation,
+    contentPadding = contentPadding,
+    windowInsets = windowInsets,
+    scrollBehavior = null
+)
+
+/**
+ * <a href="https://m3.material.io/components/bottom-app-bar/overview" class="external" target="_blank">Material Design bottom app bar</a>.
+ *
+ * A bottom app bar displays navigation and key actions at the bottom of mobile screens.
+ *
+ * ![Bottom app bar image](https://developer.android.com/images/reference/androidx/compose/material3/bottom-app-bar.png)
+ *
+ * @sample androidx.compose.material3.samples.SimpleBottomAppBar
+ *
+ * It can optionally display a [FloatingActionButton] embedded at the end of the BottomAppBar.
+ *
+ * @sample androidx.compose.material3.samples.BottomAppBarWithFAB
+ *
+ * A bottom app bar that uses a [scrollBehavior] to customize its nested scrolling behavior when
+ * working in conjunction with a scrolling content looks like:
+ *
+ * @sample androidx.compose.material3.samples.ExitAlwaysBottomAppBar
+ *
+ * Also see [NavigationBar].
+ *
+ * @param actions the icon content of this BottomAppBar. The default layout here is a [Row],
+ * so content inside will be placed horizontally.
+ * @param modifier the [Modifier] to be applied to this BottomAppBar
+ * @param floatingActionButton optional floating action button at the end of this BottomAppBar
+ * @param containerColor the color used for the background of this BottomAppBar. Use
+ * [Color.Transparent] to have no color.
+ * @param contentColor the preferred color for content inside this BottomAppBar. Defaults to either
+ * the matching content color for [containerColor], or to the current [LocalContentColor] if
+ * [containerColor] is not a color from the theme.
+ * @param tonalElevation when [containerColor] is [ColorScheme.surface], a translucent primary color
+ * overlay is applied on top of the container. A higher tonal elevation value will result in a
+ * darker color in light theme and lighter color in dark theme. See also: [Surface].
+ * @param contentPadding the padding applied to the content of this BottomAppBar
+ * @param windowInsets a window insets that app bar will respect.
+ * @param scrollBehavior a [BottomAppBarScrollBehavior] which holds various offset values that will
+ * be applied by this bottom app bar to set up its height. A scroll behavior is designed to
+ * work in conjunction with a scrolled content to change the bottom app bar appearance as the
+ * content scrolls. See [BottomAppBarScrollBehavior.nestedScrollConnection].
+ */
+@ExperimentalMaterial3Api
+@Composable
+fun BottomAppBar(
+    actions: @Composable RowScope.() -> Unit,
+    modifier: Modifier = Modifier,
+    floatingActionButton: @Composable (() -> Unit)? = null,
+    containerColor: Color = BottomAppBarDefaults.containerColor,
+    contentColor: Color = contentColorFor(containerColor),
+    tonalElevation: Dp = BottomAppBarDefaults.ContainerElevation,
+    contentPadding: PaddingValues = BottomAppBarDefaults.ContentPadding,
+    windowInsets: WindowInsets = BottomAppBarDefaults.windowInsets,
+    scrollBehavior: BottomAppBarScrollBehavior? = null,
+) = BottomAppBar(
     modifier = modifier,
     containerColor = containerColor,
     contentColor = contentColor,
     tonalElevation = tonalElevation,
     windowInsets = windowInsets,
-    contentPadding = contentPadding
+    contentPadding = contentPadding,
+    scrollBehavior = scrollBehavior
 ) {
     Row(
         modifier = Modifier.weight(1f),
@@ -455,6 +521,7 @@
  * @param content the content of this BottomAppBar. The default layout here is a [Row],
  * so content inside will be placed horizontally.
  */
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun BottomAppBar(
     modifier: Modifier = Modifier,
@@ -464,7 +531,81 @@
     contentPadding: PaddingValues = BottomAppBarDefaults.ContentPadding,
     windowInsets: WindowInsets = BottomAppBarDefaults.windowInsets,
     content: @Composable RowScope.() -> Unit
+) = BottomAppBar(
+    modifier = modifier,
+    containerColor = containerColor,
+    contentColor = contentColor,
+    tonalElevation = tonalElevation,
+    contentPadding = contentPadding,
+    windowInsets = windowInsets,
+    scrollBehavior = null,
+    content = content
+)
+
+/**
+ * <a href="https://m3.material.io/components/bottom-app-bar/overview" class="external" target="_blank">Material Design bottom app bar</a>.
+ *
+ * A bottom app bar displays navigation and key actions at the bottom of mobile screens.
+ *
+ * ![Bottom app bar image](https://developer.android.com/images/reference/androidx/compose/material3/bottom-app-bar.png)
+ *
+ * If you are interested in displaying a [FloatingActionButton], consider using another overload.
+ *
+ * Also see [NavigationBar].
+ *
+ * @param modifier the [Modifier] to be applied to this BottomAppBar
+ * @param containerColor the color used for the background of this BottomAppBar. Use
+ * [Color.Transparent] to have no color.
+ * @param contentColor the preferred color for content inside this BottomAppBar. Defaults to either
+ * the matching content color for [containerColor], or to the current [LocalContentColor] if
+ * [containerColor] is not a color from the theme.
+ * @param tonalElevation when [containerColor] is [ColorScheme.surface], a translucent primary color
+ * overlay is applied on top of the container. A higher tonal elevation value will result in a
+ * darker color in light theme and lighter color in dark theme. See also: [Surface].
+ * @param contentPadding the padding applied to the content of this BottomAppBar
+ * @param windowInsets a window insets that app bar will respect.
+ * @param scrollBehavior a [BottomAppBarScrollBehavior] which holds various offset values that will
+ * be applied by this bottom app bar to set up its height. A scroll behavior is designed to
+ * work in conjunction with a scrolled content to change the bottom app bar appearance as the
+ * content scrolls. See [BottomAppBarScrollBehavior.nestedScrollConnection].
+ * @param content the content of this BottomAppBar. The default layout here is a [Row],
+ * so content inside will be placed horizontally.
+ */
+@ExperimentalMaterial3Api
+@Composable
+fun BottomAppBar(
+    modifier: Modifier = Modifier,
+    containerColor: Color = BottomAppBarDefaults.containerColor,
+    contentColor: Color = contentColorFor(containerColor),
+    tonalElevation: Dp = BottomAppBarDefaults.ContainerElevation,
+    contentPadding: PaddingValues = BottomAppBarDefaults.ContentPadding,
+    windowInsets: WindowInsets = BottomAppBarDefaults.windowInsets,
+    scrollBehavior: BottomAppBarScrollBehavior? = null,
+    content: @Composable RowScope.() -> Unit
 ) {
+    // Set up support for resizing the bottom app bar when vertically dragging the bar itself.
+    val appBarDragModifier = if (scrollBehavior != null && !scrollBehavior.isPinned) {
+        Modifier.draggable(
+            orientation = Orientation.Vertical,
+            state = rememberDraggableState { delta ->
+                scrollBehavior.state.heightOffset -= delta
+            },
+            onDragStopped = { velocity ->
+                settleAppBarBottom(
+                    scrollBehavior.state,
+                    velocity,
+                    scrollBehavior.flingAnimationSpec,
+                    scrollBehavior.snapAnimationSpec
+                )
+            }
+        )
+    } else {
+        Modifier
+    }
+
+    // Compose a Surface with a Row content.
+    // The height of the app bar is determined by subtracting the bar's height offset from the
+    // app bar's defined constant height value (i.e. the ContainerHeight token).
     Surface(
         color = containerColor,
         contentColor = contentColor,
@@ -472,6 +613,19 @@
         // TODO(b/209583788): Consider adding a shape parameter if updated design guidance allows
         shape = BottomAppBarTokens.ContainerShape.value,
         modifier = modifier
+            .layout { measurable, constraints ->
+                // Sets the app bar's height offset to collapse the entire bar's height when content
+                // is scrolled.
+                scrollBehavior?.state?.heightOffsetLimit =
+                    -BottomAppBarTokens.ContainerHeight.toPx()
+
+                val placeable = measurable.measure(constraints)
+                val height = placeable.height + (scrollBehavior?.state?.heightOffset ?: 0f)
+                layout(placeable.width, height.roundToInt()) {
+                    placeable.place(0, 0)
+                }
+            }
+            .then(appBarDragModifier)
     ) {
         Row(
             Modifier
@@ -979,6 +1133,50 @@
     }
 }
 
+/**
+ * A BottomAppBarScrollBehavior defines how a bottom app bar should behave when the content under
+ * it is scrolled.
+ *
+ * @see [BottomAppBarDefaults.exitAlwaysScrollBehavior]
+ */
+@ExperimentalMaterial3Api
+@Stable
+interface BottomAppBarScrollBehavior {
+
+    /**
+     * A [BottomAppBarState] that is attached to this behavior and is read and updated when
+     * scrolling happens.
+     */
+    val state: BottomAppBarState
+
+    /**
+     * Indicates whether the bottom app bar is pinned.
+     *
+     * A pinned app bar will stay fixed in place when content is scrolled and will not react to any
+     * drag gestures.
+     */
+    val isPinned: Boolean
+
+    /**
+     * An optional [AnimationSpec] that defines how the bottom app bar snaps to either fully
+     * collapsed or fully extended state when a fling or a drag scrolled it into an intermediate
+     * position.
+     */
+    val snapAnimationSpec: AnimationSpec<Float>?
+
+    /**
+     * An optional [DecayAnimationSpec] that defined how to fling the bottom app bar when the user
+     * flings the app bar itself, or the content below it.
+     */
+    val flingAnimationSpec: DecayAnimationSpec<Float>?
+
+    /**
+     * A [NestedScrollConnection] that should be attached to a [Modifier.nestedScroll] in order to
+     * keep track of the scroll events.
+     */
+    val nestedScrollConnection: NestedScrollConnection
+}
+
 /** Contains default values used for the bottom app bar implementations. */
 object BottomAppBarDefaults {
 
@@ -1012,6 +1210,287 @@
     val bottomAppBarFabColor: Color
         @Composable get() =
             FabSecondaryTokens.ContainerColor.value
+
+    /**
+     * Returns a [BottomAppBarScrollBehavior]. A bottom app bar that is set up with this
+     * [BottomAppBarScrollBehavior] will immediately collapse when the content is pulled up, and
+     * will immediately appear when the content is pulled down.
+     *
+     * @param state the state object to be used to control or observe the bottom app bar's scroll
+     * state. See [rememberBottomAppBarState] for a state that is remembered across compositions.
+     * @param canScroll a callback used to determine whether scroll events are to be
+     * handled by this [ExitAlwaysScrollBehavior]
+     * @param snapAnimationSpec an optional [AnimationSpec] that defines how the bottom app bar
+     * snaps to either fully collapsed or fully extended state when a fling or a drag scrolled it
+     * into an intermediate position
+     * @param flingAnimationSpec an optional [DecayAnimationSpec] that defined how to fling the
+     * bottom app bar when the user flings the app bar itself, or the content below it
+     */
+    @ExperimentalMaterial3Api
+    @Composable
+    fun exitAlwaysScrollBehavior(
+        state: BottomAppBarState = rememberBottomAppBarState(),
+        canScroll: () -> Boolean = { true },
+        snapAnimationSpec: AnimationSpec<Float>? = spring(stiffness = Spring.StiffnessMediumLow),
+        flingAnimationSpec: DecayAnimationSpec<Float>? = rememberSplineBasedDecay()
+    ): BottomAppBarScrollBehavior =
+        ExitAlwaysScrollBehavior(
+            state = state,
+            snapAnimationSpec = snapAnimationSpec,
+            flingAnimationSpec = flingAnimationSpec,
+            canScroll = canScroll
+        )
+}
+
+/**
+ * Creates a [BottomAppBarState] that is remembered across compositions.
+ *
+ * @param initialHeightOffsetLimit the initial value for [BottomAppBarState.heightOffsetLimit],
+ * which represents the pixel limit that a bottom app bar is allowed to collapse when the scrollable
+ * content is scrolled
+ * @param initialHeightOffset the initial value for [BottomAppBarState.heightOffset]. The initial
+ * offset height offset should be between zero and [initialHeightOffsetLimit].
+ * @param initialContentOffset the initial value for [BottomAppBarState.contentOffset]
+ */
+@ExperimentalMaterial3Api
+@Composable
+fun rememberBottomAppBarState(
+    initialHeightOffsetLimit: Float = -Float.MAX_VALUE,
+    initialHeightOffset: Float = 0f,
+    initialContentOffset: Float = 0f
+): BottomAppBarState {
+    return rememberSaveable(saver = BottomAppBarState.Saver) {
+        BottomAppBarState(
+            initialHeightOffsetLimit,
+            initialHeightOffset,
+            initialContentOffset
+        )
+    }
+}
+
+/**
+ * A state object that can be hoisted to control and observe the bottom app bar state. The state is
+ * read and updated by a [BottomAppBarScrollBehavior] implementation.
+ *
+ * In most cases, this state will be created via [rememberBottomAppBarState].
+ */
+@ExperimentalMaterial3Api
+interface BottomAppBarState {
+
+    /**
+     * The bottom app bar's height offset limit in pixels, which represents the limit that a bottom
+     * app bar is allowed to collapse to.
+     *
+     * Use this limit to coerce the [heightOffset] value when it's updated.
+     */
+    var heightOffsetLimit: Float
+
+    /**
+     * The bottom app bar's current height offset in pixels. This height offset is applied to the
+     * fixed height of the app bar to control the displayed height when content is being scrolled.
+     *
+     * Updates to the [heightOffset] value are coerced between zero and [heightOffsetLimit].
+     */
+    var heightOffset: Float
+
+    /**
+     * The total offset of the content scrolled under the bottom app bar.
+     *
+     * This value is updated by a [BottomAppBarScrollBehavior] whenever a nested scroll connection
+     * consumes scroll events. A common implementation would update the value to be the sum of all
+     * [NestedScrollConnection.onPostScroll] `consumed.y` values.
+     */
+    var contentOffset: Float
+
+    /**
+     * A value that represents the collapsed height percentage of the app bar.
+     *
+     * A `0.0` represents a fully expanded bar, and `1.0` represents a fully collapsed bar (computed
+     * as [heightOffset] / [heightOffsetLimit]).
+     */
+    val collapsedFraction: Float
+
+    companion object {
+        /**
+         * The default [Saver] implementation for [BottomAppBarState].
+         */
+        val Saver: Saver<BottomAppBarState, *> = listSaver(
+            save = { listOf(it.heightOffsetLimit, it.heightOffset, it.contentOffset) },
+            restore = {
+                BottomAppBarState(
+                    initialHeightOffsetLimit = it[0],
+                    initialHeightOffset = it[1],
+                    initialContentOffset = it[2]
+                )
+            }
+        )
+    }
+}
+
+/**
+ * Creates a [BottomAppBarState].
+ *
+ * @param initialHeightOffsetLimit the initial value for [BottomAppBarState.heightOffsetLimit],
+ * which represents the pixel limit that a bottom app bar is allowed to collapse when the scrollable
+ * content is scrolled
+ * @param initialHeightOffset the initial value for [BottomAppBarState.heightOffset]. The initial
+ * offset height offset should be between zero and [initialHeightOffsetLimit].
+ * @param initialContentOffset the initial value for [BottomAppBarState.contentOffset]
+ */
+@ExperimentalMaterial3Api
+fun BottomAppBarState(
+    initialHeightOffsetLimit: Float,
+    initialHeightOffset: Float,
+    initialContentOffset: Float
+): BottomAppBarState = BottomAppBarStateImpl(
+    initialHeightOffsetLimit,
+    initialHeightOffset,
+    initialContentOffset
+)
+
+@ExperimentalMaterial3Api
+@Stable
+private class BottomAppBarStateImpl(
+    initialHeightOffsetLimit: Float,
+    initialHeightOffset: Float,
+    initialContentOffset: Float
+) : BottomAppBarState {
+
+    override var heightOffsetLimit by mutableFloatStateOf(initialHeightOffsetLimit)
+
+    override var heightOffset: Float
+        get() = _heightOffset.floatValue
+        set(newOffset) {
+            _heightOffset.floatValue = newOffset.coerceIn(
+                minimumValue = heightOffsetLimit,
+                maximumValue = 0f
+            )
+        }
+
+    override var contentOffset by mutableFloatStateOf(initialContentOffset)
+
+    override val collapsedFraction: Float
+        get() = if (heightOffsetLimit != 0f) {
+            heightOffset / heightOffsetLimit
+        } else {
+            0f
+        }
+
+    private var _heightOffset = mutableFloatStateOf(initialHeightOffset)
+}
+
+/**
+ * A [BottomAppBarScrollBehavior] that adjusts its properties to affect the colors and height of a
+ * bottom app bar.
+ *
+ * A bottom app bar that is set up with this [BottomAppBarScrollBehavior] will immediately collapse
+ * when the nested content is pulled up, and will immediately appear when the content is pulled
+ * down.
+ *
+ * @param state a [BottomAppBarState]
+ * @param snapAnimationSpec an optional [AnimationSpec] that defines how the bottom app bar snaps to
+ * either fully collapsed or fully extended state when a fling or a drag scrolled it into an
+ * intermediate position
+ * @param flingAnimationSpec an optional [DecayAnimationSpec] that defined how to fling the bottom
+ * app bar when the user flings the app bar itself, or the content below it
+ * @param canScroll a callback used to determine whether scroll events are to be
+ * handled by this [ExitAlwaysScrollBehavior]
+ */
+@ExperimentalMaterial3Api
+private class ExitAlwaysScrollBehavior(
+    override val state: BottomAppBarState,
+    override val snapAnimationSpec: AnimationSpec<Float>?,
+    override val flingAnimationSpec: DecayAnimationSpec<Float>?,
+    val canScroll: () -> Boolean = { true }
+) : BottomAppBarScrollBehavior {
+    override val isPinned: Boolean = false
+    override var nestedScrollConnection =
+        object : NestedScrollConnection {
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                if (!canScroll()) return Offset.Zero
+                state.contentOffset += consumed.y
+                if (state.heightOffset == 0f || state.heightOffset == state.heightOffsetLimit) {
+                    if (consumed.y == 0f && available.y > 0f) {
+                        // Reset the total content offset to zero when scrolling all the way down.
+                        // This will eliminate some float precision inaccuracies.
+                        state.contentOffset = 0f
+                    }
+                }
+                state.heightOffset = state.heightOffset + consumed.y
+                return Offset.Zero
+            }
+
+            override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+                val superConsumed = super.onPostFling(consumed, available)
+                return superConsumed + settleAppBarBottom(
+                    state,
+                    available.y,
+                    flingAnimationSpec,
+                    snapAnimationSpec
+                )
+            }
+        }
+}
+
+/**
+ * Settles the app bar by flinging, in case the given velocity is greater than zero, and snapping
+ * after the fling settles.
+ */
+@ExperimentalMaterial3Api
+private suspend fun settleAppBarBottom(
+    state: BottomAppBarState,
+    velocity: Float,
+    flingAnimationSpec: DecayAnimationSpec<Float>?,
+    snapAnimationSpec: AnimationSpec<Float>?
+): Velocity {
+    // Check if the app bar is completely collapsed/expanded. If so, no need to settle the app bar,
+    // and just return Zero Velocity.
+    // Note that we don't check for 0f due to float precision with the collapsedFraction
+    // calculation.
+    if (state.collapsedFraction < 0.01f || state.collapsedFraction == 1f) {
+        return Velocity.Zero
+    }
+    var remainingVelocity = velocity
+    // In case there is an initial velocity that was left after a previous user fling, animate to
+    // continue the motion to expand or collapse the app bar.
+    if (flingAnimationSpec != null && abs(velocity) > 1f) {
+        var lastValue = 0f
+        AnimationState(
+            initialValue = 0f,
+            initialVelocity = velocity,
+        )
+            .animateDecay(flingAnimationSpec) {
+                val delta = value - lastValue
+                val initialHeightOffset = state.heightOffset
+                state.heightOffset = initialHeightOffset + delta
+                val consumed = abs(initialHeightOffset - state.heightOffset)
+                lastValue = value
+                remainingVelocity = this.velocity
+                // avoid rounding errors and stop if anything is unconsumed
+                if (abs(delta - consumed) > 0.5f) this.cancelAnimation()
+            }
+    }
+    // Snap if animation specs were provided.
+    if (snapAnimationSpec != null) {
+        if (state.heightOffset < 0 &&
+            state.heightOffset > state.heightOffsetLimit
+        ) {
+            AnimationState(initialValue = state.heightOffset).animateTo(
+                if (state.collapsedFraction < 0.5f) {
+                    0f
+                } else {
+                    state.heightOffsetLimit
+                },
+                animationSpec = snapAnimationSpec
+            ) { state.heightOffset = value }
+        }
+    }
+
+    return Velocity(0f, remainingVelocity)
 }
 
 // Padding minus IconButton's min touch target expansion
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Button.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Button.kt
index 88b46ce..259114e 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Button.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Button.kt
@@ -117,7 +117,7 @@
     val containerColor = colors.containerColor(enabled).value
     val contentColor = colors.contentColor(enabled).value
     val shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp
-    val tonalElevation = elevation?.tonalElevation(enabled, interactionSource)?.value ?: 0.dp
+    val tonalElevation = elevation?.tonalElevation(enabled) ?: 0.dp
     Surface(
         onClick = onClick,
         modifier = modifier.semantics { role = Role.Button },
@@ -769,8 +769,7 @@
     private val disabledElevation: Dp,
 ) {
     /**
-     * Represents the tonal elevation used in a button, depending on its [enabled] state and
-     * [interactionSource]. This should typically be the same value as the [shadowElevation].
+     * Represents the tonal elevation used in a button, depending on its [enabled] state.
      *
      * Tonal elevation is used to apply a color shift to the surface to give the it higher emphasis.
      * When surface's color is [ColorScheme.surface], a higher elevation will result in a darker
@@ -779,16 +778,14 @@
      * See [shadowElevation] which controls the elevation of the shadow drawn around the button.
      *
      * @param enabled whether the button is enabled
-     * @param interactionSource the [InteractionSource] for this button
      */
-    @Composable
-    internal fun tonalElevation(enabled: Boolean, interactionSource: InteractionSource): State<Dp> {
-        return animateElevation(enabled = enabled, interactionSource = interactionSource)
+    internal fun tonalElevation(enabled: Boolean): Dp {
+        return if (enabled) defaultElevation else disabledElevation
     }
 
     /**
      * Represents the shadow elevation used in a button, depending on its [enabled] state and
-     * [interactionSource]. This should typically be the same value as the [tonalElevation].
+     * [interactionSource].
      *
      * Shadow elevation is used to apply a shadow around the button to give it higher emphasis.
      *
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Card.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Card.kt
index 8a55fb0..bbec175 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Card.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Card.kt
@@ -86,7 +86,7 @@
         shape = shape,
         color = colors.containerColor(enabled = true).value,
         contentColor = colors.contentColor(enabled = true).value,
-        tonalElevation = elevation.tonalElevation(enabled = true, interactionSource = null).value,
+        tonalElevation = elevation.tonalElevation(enabled = true),
         shadowElevation = elevation.shadowElevation(enabled = true, interactionSource = null).value,
         border = border,
     ) {
@@ -147,7 +147,7 @@
         shape = shape,
         color = colors.containerColor(enabled).value,
         contentColor = colors.contentColor(enabled).value,
-        tonalElevation = elevation.tonalElevation(enabled, interactionSource).value,
+        tonalElevation = elevation.tonalElevation(enabled),
         shadowElevation = elevation.shadowElevation(enabled, interactionSource).value,
         border = border,
         interactionSource = interactionSource,
@@ -564,8 +564,7 @@
     private val disabledElevation: Dp
 ) {
     /**
-     * Represents the tonal elevation used in a card, depending on its [enabled] state and
-     * [interactionSource]. This should typically be the same value as the [shadowElevation].
+     * Represents the tonal elevation used in a card, depending on its [enabled].
      *
      * Tonal elevation is used to apply a color shift to the surface to give the it higher emphasis.
      * When surface's color is [ColorScheme.surface], a higher elevation will result in a darker
@@ -574,22 +573,13 @@
      * See [shadowElevation] which controls the elevation of the shadow drawn around the card.
      *
      * @param enabled whether the card is enabled
-     * @param interactionSource the [InteractionSource] for this card
      */
-    @Composable
-    internal fun tonalElevation(
-        enabled: Boolean,
-        interactionSource: InteractionSource?
-    ): State<Dp> {
-        if (interactionSource == null) {
-            return remember { mutableStateOf(defaultElevation) }
-        }
-        return animateElevation(enabled = enabled, interactionSource = interactionSource)
-    }
+    internal fun tonalElevation(enabled: Boolean): Dp =
+        if (enabled) defaultElevation else disabledElevation
 
     /**
      * Represents the shadow elevation used in a card, depending on its [enabled] state and
-     * [interactionSource]. This should typically be the same value as the [tonalElevation].
+     * [interactionSource].
      *
      * Shadow elevation is used to apply a shadow around the card to give it higher emphasis.
      *
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt
index 4f00d2c..09096d6 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt
@@ -1325,7 +1325,7 @@
         enabled = enabled,
         shape = shape,
         color = colors.containerColor(enabled).value,
-        tonalElevation = elevation?.tonalElevation(enabled, interactionSource)?.value ?: 0.dp,
+        tonalElevation = elevation?.tonalElevation(enabled) ?: 0.dp,
         shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp,
         border = border,
         interactionSource = interactionSource,
@@ -1373,7 +1373,7 @@
         enabled = enabled,
         shape = shape,
         color = colors.containerColor(enabled, selected).value,
-        tonalElevation = elevation?.tonalElevation(enabled, interactionSource)?.value ?: 0.dp,
+        tonalElevation = elevation?.tonalElevation(enabled) ?: 0.dp,
         shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp,
         border = border,
         interactionSource = interactionSource,
@@ -1454,8 +1454,7 @@
     private val disabledElevation: Dp
 ) {
     /**
-     * Represents the tonal elevation used in a chip, depending on its [enabled] state and
-     * [interactionSource]. This should typically be the same value as the [shadowElevation].
+     * Represents the tonal elevation used in a chip, depending on its [enabled] state.
      *
      * Tonal elevation is used to apply a color shift to the surface to give the it higher emphasis.
      * When surface's color is [ColorScheme.surface], a higher elevation will result in a darker
@@ -1464,19 +1463,14 @@
      * See [shadowElevation] which controls the elevation of the shadow drawn around the chip.
      *
      * @param enabled whether the chip is enabled
-     * @param interactionSource the [InteractionSource] for this chip
      */
-    @Composable
-    internal fun tonalElevation(
-        enabled: Boolean,
-        interactionSource: InteractionSource
-    ): State<Dp> {
-        return animateElevation(enabled = enabled, interactionSource = interactionSource)
+    internal fun tonalElevation(enabled: Boolean): Dp {
+        return if (enabled) elevation else disabledElevation
     }
 
     /**
      * Represents the shadow elevation used in a chip, depending on its [enabled] state and
-     * [interactionSource]. This should typically be the same value as the [tonalElevation].
+     * [interactionSource].
      *
      * Shadow elevation is used to apply a shadow around the chip to give it higher emphasis.
      *
@@ -1617,8 +1611,7 @@
     val disabledElevation: Dp
 ) {
     /**
-     * Represents the tonal elevation used in a chip, depending on [enabled] and
-     * [interactionSource]. This should typically be the same value as the [shadowElevation].
+     * Represents the tonal elevation used in a chip, depending on [enabled].
      *
      * Tonal elevation is used to apply a color shift to the surface to give the it higher emphasis.
      * When surface's color is [ColorScheme.surface], a higher elevation will result in a darker
@@ -1627,19 +1620,14 @@
      * See [shadowElevation] which controls the elevation of the shadow drawn around the Chip.
      *
      * @param enabled whether the chip is enabled
-     * @param interactionSource the [InteractionSource] for this chip
      */
-    @Composable
-    internal fun tonalElevation(
-        enabled: Boolean,
-        interactionSource: InteractionSource
-    ): State<Dp> {
-        return animateElevation(enabled = enabled, interactionSource = interactionSource)
+    internal fun tonalElevation(enabled: Boolean): Dp {
+        return if (enabled) elevation else disabledElevation
     }
 
     /**
      * Represents the shadow elevation used in a chip, depending on [enabled] and
-     * [interactionSource]. This should typically be the same value as the [tonalElevation].
+     * [interactionSource].
      *
      * Shadow elevation is used to apply a shadow around the surface to give it higher emphasis.
      *
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt
index ae586c8..dd8ce60 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/FloatingActionButton.kt
@@ -108,7 +108,7 @@
         shape = shape,
         color = containerColor,
         contentColor = contentColor,
-        tonalElevation = elevation.tonalElevation(interactionSource = interactionSource).value,
+        tonalElevation = elevation.tonalElevation(),
         shadowElevation = elevation.shadowElevation(interactionSource = interactionSource).value,
         interactionSource = interactionSource,
     ) {
@@ -496,9 +496,8 @@
         return animateElevation(interactionSource = interactionSource)
     }
 
-    @Composable
-    internal fun tonalElevation(interactionSource: InteractionSource): State<Dp> {
-        return animateElevation(interactionSource = interactionSource)
+    internal fun tonalElevation(): Dp {
+        return defaultElevation
     }
 
     @Composable
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Label.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Label.kt
new file mode 100644
index 0000000..1aedef2
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Label.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.foundation.BasicTooltipBox
+import androidx.compose.foundation.BasicTooltipState
+import androidx.compose.foundation.MutatePriority
+import androidx.compose.foundation.MutatorMutex
+import androidx.compose.foundation.interaction.DragInteraction
+import androidx.compose.foundation.interaction.HoverInteraction
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.foundation.rememberBasicTooltipState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import kotlinx.coroutines.flow.collectLatest
+
+/**
+ * Label component that will append a [label] to [content].
+ * The positioning logic uses [TooltipDefaults.rememberPlainTooltipPositionProvider].
+ *
+ * Label appended to thumbs of Slider:
+ *
+ * @sample androidx.compose.material3.samples.SliderWithCustomThumbSample
+ *
+ * Label appended to thumbs of RangeSlider:
+ *
+ * @sample androidx.compose.material3.samples.RangeSliderWithCustomComponents
+ *
+ * @param label composable that will be appended to [content]
+ * @param modifier [Modifier] that will be applied to [content]
+ * @param interactionSource the [MutableInteractionSource] representing the
+ * stream of [Interaction]s for the [content].
+ * @param isPersistent boolean to determine if the label should be persistent.
+ * If true, then the label will always show and be anchored to [content].
+ * if false, then the label will only show when pressing down or hovering over the [content].
+ * @param content the composable that [label] will anchor to.
+ */
+@ExperimentalMaterial3Api
+@Composable
+fun Label(
+    label: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    isPersistent: Boolean = false,
+    content: @Composable () -> Unit
+) {
+    // Has the same positioning logic as PlainTooltips
+    val positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider()
+    val state = if (isPersistent)
+        remember { LabelStateImpl() }
+    else
+        rememberBasicTooltipState(mutatorMutex = MutatorMutex())
+    BasicTooltipBox(
+        positionProvider = positionProvider,
+        tooltip = label,
+        state = state,
+        modifier = modifier,
+        focusable = false,
+        enableUserInput = false,
+        content = content
+    )
+    HandleInteractions(
+        enabled = !isPersistent,
+        state = state,
+        interactionSource = interactionSource
+    )
+}
+
+@Composable
+private fun HandleInteractions(
+    enabled: Boolean,
+    state: BasicTooltipState,
+    interactionSource: MutableInteractionSource
+) {
+    if (enabled) {
+        LaunchedEffect(interactionSource) {
+            interactionSource.interactions.collectLatest { interaction ->
+                when (interaction) {
+                    is PressInteraction.Press,
+                    is DragInteraction.Start,
+                    is HoverInteraction.Enter -> { state.show(MutatePriority.UserInput) }
+                    is PressInteraction.Release,
+                    is DragInteraction.Stop,
+                    is HoverInteraction.Exit -> { state.dismiss() }
+                }
+            }
+        }
+    }
+}
+
+private class LabelStateImpl(
+    override val isVisible: Boolean = true,
+    override val isPersistent: Boolean = true
+) : BasicTooltipState {
+    override suspend fun show(mutatePriority: MutatePriority) {}
+
+    override fun dismiss() {}
+
+    override fun onDispose() {}
+}
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 b346d44..4e2d001 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
@@ -27,7 +27,10 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 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.runtime.staticCompositionLocalOf
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -126,6 +129,7 @@
  * @param bottomBar the content to place at the bottom of the [Scaffold], on top of the
  * [content], typically a [NavigationBar].
  */
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun ScaffoldLayout(
     fabPosition: FabPosition,
@@ -135,7 +139,42 @@
     fab: @Composable () -> Unit,
     contentWindowInsets: WindowInsets,
     bottomBar: @Composable () -> Unit
+) {
+    if (ScaffoldSubcomposeInMeasureFix) {
+        ScaffoldLayoutWithMeasureFix(
+            fabPosition = fabPosition,
+            topBar = topBar,
+            content = content,
+            snackbar = snackbar,
+            fab = fab,
+            contentWindowInsets = contentWindowInsets,
+            bottomBar = bottomBar
+        )
+    } else {
+        LegacyScaffoldLayout(
+            fabPosition = fabPosition,
+            topBar = topBar,
+            content = content,
+            snackbar = snackbar,
+            fab = fab,
+            contentWindowInsets = contentWindowInsets,
+            bottomBar = bottomBar
+        )
+    }
+}
 
+/**
+ * Layout for a [Scaffold]'s content, subcomposing and measuring during measurement.
+ */
+@Composable
+private fun ScaffoldLayoutWithMeasureFix(
+    fabPosition: FabPosition,
+    topBar: @Composable () -> Unit,
+    content: @Composable (PaddingValues) -> Unit,
+    snackbar: @Composable () -> Unit,
+    fab: @Composable () -> Unit,
+    contentWindowInsets: WindowInsets,
+    bottomBar: @Composable () -> Unit
 ) {
     SubcomposeLayout { constraints ->
         val layoutWidth = constraints.maxWidth
@@ -197,7 +236,7 @@
                         layoutWidth - FabSpacing.roundToPx() - fabWidth
                     }
                 }
-                FabPosition.End -> {
+                FabPosition.End, FabPosition.EndOverlay -> {
                     if (layoutDirection == LayoutDirection.Ltr) {
                         layoutWidth - FabSpacing.roundToPx() - fabWidth
                     } else {
@@ -225,7 +264,7 @@
 
         val bottomBarHeight = bottomBarPlaceables.fastMaxBy { it.height }?.height
         val fabOffsetFromBottom = fabPlacement?.let {
-            if (bottomBarHeight == null) {
+            if (bottomBarHeight == null || fabPosition == FabPosition.EndOverlay) {
                 it.height + FabSpacing.roundToPx() +
                     contentWindowInsets.getBottom(this@SubcomposeLayout)
             } else {
@@ -295,6 +334,175 @@
 }
 
 /**
+ * Layout for a [Scaffold]'s content, subcomposing and measuring during measurement.
+ */
+@Composable
+private fun LegacyScaffoldLayout(
+    fabPosition: FabPosition,
+    topBar: @Composable () -> Unit,
+    content: @Composable (PaddingValues) -> Unit,
+    snackbar: @Composable () -> Unit,
+    fab: @Composable () -> Unit,
+    contentWindowInsets: WindowInsets,
+    bottomBar: @Composable () -> Unit
+) {
+    SubcomposeLayout { constraints ->
+        val layoutWidth = constraints.maxWidth
+        val layoutHeight = constraints.maxHeight
+
+        val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
+
+        layout(layoutWidth, layoutHeight) {
+            val topBarPlaceables = subcompose(ScaffoldLayoutContent.TopBar, topBar).fastMap {
+                it.measure(looseConstraints)
+            }
+
+            val topBarHeight = topBarPlaceables.fastMaxBy { it.height }?.height ?: 0
+
+            val snackbarPlaceables = subcompose(ScaffoldLayoutContent.Snackbar, snackbar).fastMap {
+                // respect only bottom and horizontal for snackbar and fab
+                val leftInset = contentWindowInsets
+                    .getLeft(this@SubcomposeLayout, layoutDirection)
+                val rightInset = contentWindowInsets
+                    .getRight(this@SubcomposeLayout, layoutDirection)
+                val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout)
+                // offset the snackbar constraints by the insets values
+                it.measure(
+                    looseConstraints.offset(
+                        -leftInset - rightInset,
+                        -bottomInset
+                    )
+                )
+            }
+
+            val snackbarHeight = snackbarPlaceables.fastMaxBy { it.height }?.height ?: 0
+            val snackbarWidth = snackbarPlaceables.fastMaxBy { it.width }?.width ?: 0
+
+            val fabPlaceables =
+                subcompose(ScaffoldLayoutContent.Fab, fab).fastMapNotNull { measurable ->
+                    // respect only bottom and horizontal for snackbar and fab
+                    val leftInset =
+                        contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection)
+                    val rightInset =
+                        contentWindowInsets.getRight(this@SubcomposeLayout, layoutDirection)
+                    val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout)
+                    measurable.measure(
+                        looseConstraints.offset(
+                            -leftInset - rightInset,
+                            -bottomInset
+                        )
+                    )
+                        .takeIf { it.height != 0 && it.width != 0 }
+                }
+
+            val fabPlacement = if (fabPlaceables.isNotEmpty()) {
+                val fabWidth = fabPlaceables.fastMaxBy { it.width }!!.width
+                val fabHeight = fabPlaceables.fastMaxBy { it.height }!!.height
+                // FAB distance from the left of the layout, taking into account LTR / RTL
+                val fabLeftOffset = when (fabPosition) {
+                    FabPosition.Start -> {
+                        if (layoutDirection == LayoutDirection.Ltr) {
+                            FabSpacing.roundToPx()
+                        } else {
+                            layoutWidth - FabSpacing.roundToPx() - fabWidth
+                        }
+                    }
+                    FabPosition.End -> {
+                        if (layoutDirection == LayoutDirection.Ltr) {
+                            layoutWidth - FabSpacing.roundToPx() - fabWidth
+                        } else {
+                            FabSpacing.roundToPx()
+                        }
+                    }
+                    else -> (layoutWidth - fabWidth) / 2
+                }
+
+                FabPlacement(
+                    left = fabLeftOffset,
+                    width = fabWidth,
+                    height = fabHeight
+                )
+            } else {
+                null
+            }
+
+            val bottomBarPlaceables = subcompose(ScaffoldLayoutContent.BottomBar) {
+                CompositionLocalProvider(
+                    LocalFabPlacement provides fabPlacement,
+                    content = bottomBar
+                )
+            }.fastMap { it.measure(looseConstraints) }
+
+            val bottomBarHeight = bottomBarPlaceables.fastMaxBy { it.height }?.height
+            val fabOffsetFromBottom = fabPlacement?.let {
+                if (bottomBarHeight == null) {
+                    it.height + FabSpacing.roundToPx() +
+                        contentWindowInsets.getBottom(this@SubcomposeLayout)
+                } else {
+                    // Total height is the bottom bar height + the FAB height + the padding
+                    // between the FAB and bottom bar
+                    bottomBarHeight + it.height + FabSpacing.roundToPx()
+                }
+            }
+
+            val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
+                snackbarHeight +
+                    (fabOffsetFromBottom ?: bottomBarHeight
+                    ?: contentWindowInsets.getBottom(this@SubcomposeLayout))
+            } else {
+                0
+            }
+
+            val bodyContentPlaceables = subcompose(ScaffoldLayoutContent.MainContent) {
+                val insets = contentWindowInsets.asPaddingValues(this@SubcomposeLayout)
+                val innerPadding = PaddingValues(
+                    top =
+                    if (topBarPlaceables.isEmpty()) {
+                        insets.calculateTopPadding()
+                    } else {
+                        topBarHeight.toDp()
+                    },
+                    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)
+            }.fastMap { it.measure(looseConstraints) }
+
+            // Placing to control drawing order to match default elevation of each placeable
+            bodyContentPlaceables.fastForEach {
+                it.place(0, 0)
+            }
+            topBarPlaceables.fastForEach {
+                it.place(0, 0)
+            }
+            snackbarPlaceables.fastForEach {
+                it.place(
+                    (layoutWidth - snackbarWidth) / 2 +
+                        contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection),
+                    layoutHeight - snackbarOffsetFromBottom
+                )
+            }
+            // The bottom bar is always at the bottom of the layout
+            bottomBarPlaceables.fastForEach {
+                it.place(0, layoutHeight - (bottomBarHeight ?: 0))
+            }
+            // Explicitly not using placeRelative here as `leftOffset` already accounts for RTL
+            fabPlacement?.let { placement ->
+                fabPlaceables.fastForEach {
+                    it.place(placement.left, layoutHeight - fabOffsetFromBottom!!)
+                }
+            }
+        }
+    }
+}
+
+/**
  * Object containing various default values for [Scaffold] component.
  */
 object ScaffoldDefaults {
@@ -329,18 +537,41 @@
          * exists)
          */
         val End = FabPosition(2)
+
+        /**
+         * Position FAB at the bottom of the screen at the end, overlaying the [NavigationBar] (if
+         * it exists)
+         */
+        val EndOverlay = FabPosition(3)
     }
 
     override fun toString(): String {
         return when (this) {
             Start -> "FabPosition.Start"
             Center -> "FabPosition.Center"
-            else -> "FabPosition.End"
+            End -> "FabPosition.End"
+            else -> "FabPosition.EndOverlay"
         }
     }
 }
 
 /**
+ * Flag indicating if [Scaffold] should subcompose and measure its children during measurement or
+ * during placement.
+ * Set this flag to false to keep Scaffold's old measurement behavior (measuring in placement).
+ *
+ * <b>This flag will be removed in Compose 1.6.0-beta01.</b> If you encounter any issues with the
+ * new behavior, please file an issue at: issuetracker.google.com/issues/new?component=742043
+ */
+// TODO(b/299621062): Remove flag before beta
+@Suppress("GetterSetterNames", "OPT_IN_MARKER_ON_WRONG_TARGET")
+@get:Suppress("GetterSetterNames")
+@get:ExperimentalMaterial3Api
+@set:ExperimentalMaterial3Api
+@ExperimentalMaterial3Api
+var ScaffoldSubcomposeInMeasureFix by mutableStateOf(true)
+
+/**
  * Placement information for a [FloatingActionButton] inside a [Scaffold].
  *
  * @property left the FAB's offset from the left edge of the bottom bar, already adjusted for RTL
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt
index 36d13cf..9f2c7b0 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SegmentedButton.kt
@@ -32,6 +32,7 @@
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.PressInteraction
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.IntrinsicSize
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.RowScope
@@ -61,12 +62,9 @@
 import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.State
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -74,6 +72,10 @@
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.TransformOrigin
 import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.MultiContentMeasurePolicy
 import androidx.compose.ui.layout.layout
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.role
@@ -85,6 +87,7 @@
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastMap
 import androidx.compose.ui.util.fastMaxBy
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 
 /**
@@ -312,57 +315,76 @@
     icon: @Composable () -> Unit,
     content: @Composable () -> Unit,
 ) {
-    Row(
-        modifier = Modifier.padding(ButtonDefaults.TextButtonContentPadding),
-        horizontalArrangement = Arrangement.Center,
-        verticalAlignment = Alignment.CenterVertically
-    ) {
-        ProvideTextStyle(value = MaterialTheme.typography.labelLarge) {
-            var animatable by remember {
-                mutableStateOf<Animatable<Int, AnimationVector1D>?>(null)
+    Box(contentAlignment = Alignment.Center) {
+        val typography =
+            MaterialTheme.typography.fromToken(OutlinedSegmentedButtonTokens.LabelTextFont)
+        ProvideTextStyle(typography) {
+            val scope = rememberCoroutineScope()
+            val measurePolicy = remember { SegmentedButtonContentMeasurePolicy(scope) }
+
+            Layout(
+                modifier = Modifier.padding(ButtonDefaults.TextButtonContentPadding),
+                contents = listOf(icon, content),
+                measurePolicy = measurePolicy
+            )
+        }
+    }
+}
+
+internal class SegmentedButtonContentMeasurePolicy(
+    val scope: CoroutineScope
+) : MultiContentMeasurePolicy {
+    var animatable: Animatable<Int, AnimationVector1D>? = null
+    private var initialOffset: Int? = null
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    override fun MeasureScope.measure(
+        measurables: List<List<Measurable>>,
+        constraints: Constraints
+    ): MeasureResult {
+        val (iconMeasurables, contentMeasurables) = measurables
+        val iconPlaceables = iconMeasurables.fastMap { it.measure(constraints) }
+        val iconDesiredWidth = iconMeasurables.fastFold(0) { acc, it ->
+            maxOf(acc, it.maxIntrinsicWidth(Constraints.Infinity))
+        }
+        val iconWidth = iconPlaceables.fastMaxBy { it.width }?.width ?: 0
+        val contentPlaceables = contentMeasurables.fastMap { it.measure(constraints) }
+        val contentWidth = contentPlaceables.fastMaxBy { it.width }?.width
+        val width = maxOf(SegmentedButtonDefaults.IconSize.roundToPx(), iconDesiredWidth) +
+            IconSpacing.roundToPx() +
+            (contentWidth ?: 0)
+
+        val offsetX = if (iconWidth == 0) {
+            -(SegmentedButtonDefaults.IconSize.roundToPx() + IconSpacing.roundToPx()) / 2
+        } else {
+            iconDesiredWidth - SegmentedButtonDefaults.IconSize.roundToPx()
+        }
+
+        if (initialOffset == null) {
+            initialOffset = offsetX
+        } else {
+            val anim = animatable ?: Animatable(initialOffset!!, Int.VectorConverter)
+                .also { animatable = it }
+            if (anim.targetValue != offsetX) {
+                scope.launch {
+                    anim.animateTo(offsetX, tween(MotionTokens.DurationMedium3.toInt()))
+                }
+            }
+        }
+
+        return layout(width, constraints.maxHeight) {
+            iconPlaceables.fastForEach {
+                it.place(0, (constraints.maxHeight - it.height) / 2)
             }
 
-            val scope = rememberCoroutineScope()
+            val contentOffsetX = SegmentedButtonDefaults.IconSize.roundToPx() +
+                IconSpacing.roundToPx() + (animatable?.value ?: offsetX)
 
-            Layout(listOf(icon, content)) { (iconMeasurables, contentMeasurables), constraints ->
-                val iconPlaceables = iconMeasurables.fastMap { it.measure(constraints) }
-                val iconDesiredWidth = iconMeasurables.fastFold(0) { acc, it ->
-                    maxOf(acc, it.maxIntrinsicWidth(Constraints.Infinity))
-                }
-                val iconWidth = iconPlaceables.fastMaxBy { it.width }?.width ?: 0
-                val contentPlaceables = contentMeasurables.fastMap { it.measure(constraints) }
-                val contentWidth = contentPlaceables.fastMaxBy { it.width }?.width
-                val width = maxOf(SegmentedButtonDefaults.IconSize.roundToPx(), iconDesiredWidth) +
-                    IconSpacing.roundToPx() +
-                    (contentWidth ?: 0)
-
-                val offsetX = if (iconWidth == 0) {
-                    -(SegmentedButtonDefaults.IconSize.roundToPx() + IconSpacing.roundToPx()) / 2
-                } else {
-                    iconDesiredWidth - SegmentedButtonDefaults.IconSize.roundToPx()
-                }
-
-                val anim = animatable ?: Animatable(offsetX, Int.VectorConverter)
-                    .also { animatable = it }
-
-                if (anim.targetValue != offsetX) {
-                    scope.launch {
-                        anim.animateTo(offsetX, tween(MotionTokens.DurationMedium3.toInt()))
-                    }
-                }
-
-                layout(width, constraints.maxHeight) {
-                    iconPlaceables.fastForEach {
-                        it.place(0, (constraints.maxHeight - it.height) / 2)
-                    }
-
-                    val contentOffsetX = SegmentedButtonDefaults.IconSize.roundToPx() +
-                        IconSpacing.roundToPx() + anim.value
-
-                    contentPlaceables.fastForEach {
-                        it.place(contentOffsetX, (constraints.maxHeight - it.height) / 2)
-                    }
-                }
+            contentPlaceables.fastForEach {
+                it.place(
+                    contentOffsetX,
+                    (constraints.maxHeight - it.height) / 2
+                )
             }
         }
     }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
index 67bdfb5..21e04e7 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
@@ -89,7 +89,7 @@
  * relative to the anchor content.
  * @param tooltip the composable that will be used to populate the tooltip's content.
  * @param state handles the state of the tooltip's visibility.
- * @param modifier the [Modifier] to be applied to the tooltip.
+ * @param modifier the [Modifier] to be applied to the TooltipBox.
  * @param focusable [Boolean] that determines if the tooltip is focusable. When true,
  * the tooltip will consume touch events while it's shown and will have accessibility
  * focus move to the first element of the component. When false, the tooltip
@@ -142,16 +142,16 @@
     content: @Composable () -> Unit
 ) {
     Surface(
-        modifier = modifier
+        shape = shape,
+        color = containerColor
+    ) {
+        Box(modifier = modifier
             .sizeIn(
                 minWidth = TooltipMinWidth,
                 maxWidth = PlainTooltipMaxWidth,
                 minHeight = TooltipMinHeight
-            ),
-        shape = shape,
-        color = containerColor
-    ) {
-        Box(modifier = Modifier.padding(PlainTooltipContentPadding)) {
+            ).padding(PlainTooltipContentPadding)
+        ) {
             val textStyle =
                 MaterialTheme.typography.fromToken(PlainTooltipTokens.SupportingTextFont)
             CompositionLocalProvider(
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 a1f94db..4e0c3fe 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
@@ -1278,7 +1278,7 @@
     private var reusingGroup = -1
     private var childrenComposing: Int = 0
     private var compositionToken: Int = 0
-    private var sourceInformationEnabled = true
+    private var sourceInformationEnabled = false
     private val derivedStateObserver = object : DerivedStateObserver {
         override fun start(derivedState: DerivedState<*>) {
             childrenComposing++
@@ -1458,6 +1458,12 @@
         if (!forceRecomposeScopes) {
             forceRecomposeScopes = parentContext.collectingParameterInformation
         }
+
+        // Propagate collecting source information
+        if (!sourceInformationEnabled) {
+            sourceInformationEnabled = parentContext.collectingSourceInformation
+        }
+
         parentProvider.read(LocalInspectionTables)?.let {
             it.add(slotTable)
             parentContext.recordInspectionTable(it)
@@ -1544,11 +1550,13 @@
         private set
 
     /**
-     * Start collecting parameter information. This enables the tools API to always be able to
-     * determine the parameter values of composable calls.
+     * Start collecting parameter information and line number information. This enables the tools
+     * API to always be able to determine the parameter values of composable calls as well as the
+     * source location of calls.
      */
     override fun collectParameterInformation() {
         forceRecomposeScopes = true
+        sourceInformationEnabled = true
     }
 
     @OptIn(InternalComposeApi::class)
@@ -2115,6 +2123,7 @@
                 CompositionContextImpl(
                     compoundKeyHash,
                     forceRecomposeScopes,
+                    sourceInformationEnabled,
                     (composition as? CompositionImpl)?.observerHolder
                 )
             )
@@ -3158,20 +3167,22 @@
     @ComposeCompilerApi
     override fun sourceInformation(sourceInformation: String) {
         if (inserting && sourceInformationEnabled) {
-            writer.insertAux(sourceInformation)
+            writer.recordGroupSourceInformation(sourceInformation)
         }
     }
 
     @ComposeCompilerApi
     override fun sourceInformationMarkerStart(key: Int, sourceInformation: String) {
-        if (sourceInformationEnabled)
-            start(key, objectKey = null, kind = GroupKind.Group, data = sourceInformation)
+        if (inserting && sourceInformationEnabled) {
+            writer.recordGrouplessCallSourceInformationStart(key, sourceInformation)
+        }
     }
 
     @ComposeCompilerApi
     override fun sourceInformationMarkerEnd() {
-        if (sourceInformationEnabled)
-            end(isNode = false)
+        if (inserting && sourceInformationEnabled) {
+            writer.recordGrouplessCallSourceInformationEnd()
+        }
     }
 
     override fun disableSourceInformation() {
@@ -3503,6 +3514,7 @@
     private inner class CompositionContextImpl(
         override val compoundHashKey: Int,
         override val collectingParameterInformation: Boolean,
+        override val collectingSourceInformation: Boolean,
         override val observerHolder: CompositionObserverHolder?
     ) : CompositionContext() {
         var inspectionTables: MutableSet<MutableSet<CompositionData>>? = null
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionContext.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionContext.kt
index 4b64873..ae6583c 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionContext.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionContext.kt
@@ -38,6 +38,7 @@
 abstract class CompositionContext internal constructor() {
     internal abstract val compoundHashKey: Int
     internal abstract val collectingParameterInformation: Boolean
+    internal abstract val collectingSourceInformation: Boolean
     internal open val observerHolder: CompositionObserverHolder? get() = null
 
     /**
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
index d75d4bf..6b38d81 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
@@ -1303,6 +1303,9 @@
     internal override val collectingParameterInformation: Boolean
         get() = false
 
+    internal override val collectingSourceInformation: Boolean
+        get() = false
+
     internal override fun recordInspectionTable(table: MutableSet<CompositionData>) {
         // TODO: The root recomposer might be a better place to set up inspection
         // than the current configuration with an CompositionLocal
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
index 32a9f5c..decf811 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-@file:OptIn(InternalComposeApi::class)
-
 package androidx.compose.runtime
 
+import androidx.compose.runtime.snapshots.fastAny
 import androidx.compose.runtime.snapshots.fastFilterIndexed
 import androidx.compose.runtime.snapshots.fastForEach
 import androidx.compose.runtime.snapshots.fastMap
@@ -133,6 +132,11 @@
     internal var anchors: ArrayList<Anchor> = arrayListOf()
 
     /**
+     * A map of source information to anchor.
+     */
+    internal var sourceInformationMap: HashMap<Anchor, GroupSourceInformation>? = null
+
+    /**
      * Returns true if the slot table is empty
      */
     override val isEmpty get() = groupsSize == 0
@@ -191,7 +195,7 @@
         runtimeCheck(readers <= 0) { "Cannot start a writer when a reader is pending" }
         writer = true
         version++
-        return SlotWriter(this)
+        return SlotWriter(table = this)
     }
 
     /**
@@ -204,7 +208,7 @@
      * at [index] is still in this table.
      */
     fun anchor(index: Int): Anchor {
-        runtimeCheck(!writer) { "use active SlotWriter to create an anchor location instead " }
+        runtimeCheck(!writer) { "use active SlotWriter to create an anchor location instead" }
         require(index in 0 until groupsSize) { "Parameter index is out of range" }
         return anchors.getOrAdd(index, groupsSize) {
             Anchor(index)
@@ -212,6 +216,14 @@
     }
 
     /**
+     * Return an anchor to the given index if there is one already, null otherwise.
+     */
+    fun tryAnchor(index: Int): Anchor? {
+        runtimeCheck(!writer) { "use active SlotWriter to crate an anchor for location instead" }
+        return if (index in 0 until groupsSize) anchors.find(index, groupsSize) else null
+    }
+
+    /**
      * Return the group index for [anchor]. This [SlotTable] is assumed to own [anchor] but that
      * is not validated. If [anchor] is not owned by this [SlotTable] the result is undefined.
      * If a [SlotWriter] is open the [SlotWriter.anchorIndex] must be called instead as [anchor]
@@ -247,9 +259,22 @@
     /**
      * Close [reader].
      */
-    internal fun close(reader: SlotReader) {
+    internal fun close(
+        reader: SlotReader,
+        sourceInformationMap: HashMap<Anchor, GroupSourceInformation>?
+    ) {
         runtimeCheck(reader.table === this && readers > 0) { "Unexpected reader close()" }
         readers--
+        if (sourceInformationMap != null) {
+            synchronized(this) {
+                val thisMap = this.sourceInformationMap
+                if (thisMap != null) {
+                    thisMap.putAll(sourceInformationMap)
+                } else {
+                    this.sourceInformationMap = sourceInformationMap
+                }
+            }
+        }
     }
 
     /**
@@ -263,11 +288,12 @@
         groupsSize: Int,
         slots: Array<Any?>,
         slotsSize: Int,
-        anchors: ArrayList<Anchor>
+        anchors: ArrayList<Anchor>,
+        sourceInformationMap: HashMap<Anchor, GroupSourceInformation>?,
     ) {
         require(writer.table === this && this.writer) { "Unexpected writer close()" }
         this.writer = false
-        setTo(groups, groupsSize, slots, slotsSize, anchors)
+        setTo(groups, groupsSize, slots, slotsSize, anchors, sourceInformationMap)
     }
 
     /**
@@ -279,7 +305,8 @@
         groupsSize: Int,
         slots: Array<Any?>,
         slotsSize: Int,
-        anchors: ArrayList<Anchor>
+        anchors: ArrayList<Anchor>,
+        sourceInformationMap: HashMap<Anchor, GroupSourceInformation>?,
     ) {
         // Adopt the slots from the writer
         this.groups = groups
@@ -287,6 +314,7 @@
         this.slots = slots
         this.slotsSize = slotsSize
         this.anchors = anchors
+        this.sourceInformationMap = sourceInformationMap
     }
 
     /**
@@ -357,6 +385,10 @@
         return groupsSize > 0 && groups.containsMark(0)
     }
 
+    fun sourceInformationOf(group: Int) = sourceInformationMap?.let { map ->
+        tryAnchor(group)?.let { anchor -> map[anchor] }
+    }
+
     /**
      * Find the nearest recompose scope for [group] that, when invalidated, will cause [group]
      * group to be recomposed.
@@ -376,7 +408,7 @@
 
     /**
      * Finds the nearest recompose scope to the provided group and invalidates it. Return
-     * true if the invalidation will cause the scope to reccompose, otherwise false which will
+     * true if the invalidation will cause the scope to recompose, otherwise false which will
      * require forcing recomposition some other way.
      */
     private fun invalidateGroup(group: Int): Boolean {
@@ -487,6 +519,35 @@
             require(lastLocation < location) { "Anchor is out of order" }
             lastLocation = location
         }
+
+        // Verify source information is well-formed
+        fun verifySourceGroup(group: GroupSourceInformation) {
+            group.groups?.fastForEach { item ->
+                when (item) {
+                    is Anchor -> {
+                        require(item.valid) {
+                            "Source map contains invalid anchor"
+                        }
+                        require(ownsAnchor(item)) {
+                            "Source map anchor is not owned by the slot table"
+                        }
+                    }
+                    is GroupSourceInformation -> verifySourceGroup(item)
+                }
+            }
+        }
+
+        sourceInformationMap?.let { sourceInformationMap ->
+            for ((anchor, sourceGroup) in sourceInformationMap) {
+                require(anchor.valid) {
+                    "Source map contains invalid anchor"
+                }
+                require(ownsAnchor(anchor)) {
+                    "Source map anchor is not owned by the slot table"
+                }
+                verifySourceGroup(sourceGroup)
+            }
+        }
     }
 
     /**
@@ -518,7 +579,21 @@
         repeat(level) { append(' ') }
         append("Group(")
         append(index)
-        append(") key=")
+        append(")")
+        tryAnchor(index)?.let { anchor ->
+            sourceInformationMap?.get(anchor)?.let { groupInformation ->
+                groupInformation.sourceInformation?.let {
+                    if (it.startsWith("C(") || it.startsWith("CC(")) {
+                        val start = it.indexOf("(") + 1
+                        val endParen = it.indexOf(')')
+                        append(" ")
+                        append(it.substring(start, endParen))
+                        append("()")
+                    }
+                }
+            }
+        }
+        append(" key=")
         append(groups.key(index))
         fun dataIndex(index: Int) =
             if (index >= groupsSize) slotsSize else groups.dataAnchor(index)
@@ -635,6 +710,105 @@
     val valid get() = location != Int.MIN_VALUE
     fun toIndexFor(slots: SlotTable) = slots.anchorIndex(this)
     fun toIndexFor(writer: SlotWriter) = writer.anchorIndex(this)
+
+    override fun toString(): String {
+        return "${super.toString()}{ location = $location }"
+    }
+}
+
+internal class GroupSourceInformation(val key: Int, var sourceInformation: String?) {
+    var groups: ArrayList<Any /* Anchor | GroupSourceInformation */>? = null
+    var closed = false
+
+    fun startGrouplessCall(key: Int, sourceInformation: String) {
+        openInformation().add(GroupSourceInformation(key, sourceInformation))
+    }
+
+    fun endGrouplessCall() { openInformation().close() }
+
+    fun reportGroup(writer: SlotWriter, group: Int) {
+        openInformation().add(writer.anchor(group))
+    }
+
+    fun reportGroup(table: SlotTable, group: Int) {
+        openInformation().add(table.anchor(group))
+    }
+
+    fun addGroupAfter(writer: SlotWriter, predecessor: Int, group: Int) {
+        val groups = groups ?: ArrayList<Any>().also { groups = it }
+        val index = if (predecessor >= 0) {
+            val anchor = writer.tryAnchor(predecessor)
+            if (anchor != null) {
+                groups.fastIndexOf {
+                    it == anchor ||
+                        (it is GroupSourceInformation && it.hasAnchor(anchor))
+                }
+            } else 0
+        } else 0
+        groups.add(index, writer.anchor(group))
+    }
+
+    fun close() { closed = true }
+
+    // Return the current open nested source information or this.
+    private fun openInformation(): GroupSourceInformation =
+        (groups?.let {
+            groups -> groups.fastLastOrNull { it is GroupSourceInformation && !it.closed }
+        } as? GroupSourceInformation)?.openInformation() ?: this
+
+    private fun add(group: Any /* Anchor | GroupSourceInformation */) {
+        val groups = groups ?: ArrayList()
+        this.groups = groups
+        groups.add(group)
+    }
+
+    private fun hasAnchor(anchor: Anchor): Boolean =
+        groups?.fastAny {
+            it == anchor || (it is GroupSourceInformation && hasAnchor(anchor))
+        } == true
+
+    fun removeAnchor(anchor: Anchor): Boolean {
+        val groups = groups
+        if (groups != null) {
+            var index = groups.size - 1
+            while (index >= 0) {
+                when (val item = groups[index]) {
+                    is Anchor -> if (item == anchor) groups.removeAt(index)
+                    is GroupSourceInformation -> if (!item.removeAnchor(anchor)) {
+                        groups.removeAt(index)
+                    }
+                }
+                index--
+            }
+            if (groups.isEmpty()) {
+                this.groups = null
+                return false
+            }
+            return true
+        }
+        return true
+    }
+}
+
+private inline fun <T> ArrayList<T>.fastLastOrNull(predicate: (T) -> Boolean): T? {
+    var index = size - 1
+    while (index >= 0) {
+        val value = get(index)
+        if (predicate(value)) return value
+        index--
+    }
+    return null
+}
+
+private inline fun <T> ArrayList<T>.fastIndexOf(predicate: (T) -> Boolean): Int {
+    var index = 0
+    val size = size
+    while (index < size) {
+        val value = get(index)
+        if (predicate(value)) return index
+        index++
+    }
+    return -1
 }
 
 /**
@@ -668,6 +842,12 @@
     private val slotsSize: Int = table.slotsSize
 
     /**
+     * A local copy of the [sourceInformationMap] being created to be merged into [table]
+     * when the reader closes.
+     */
+    private var sourceInformationMap: HashMap<Anchor, GroupSourceInformation>? = null
+
+    /**
      * True if the reader has been closed
      */
     var closed: Boolean = false
@@ -927,7 +1107,7 @@
      */
     fun close() {
         closed = true
-        table.close(this)
+        table.close(this, sourceInformationMap)
     }
 
     /**
@@ -935,14 +1115,17 @@
      */
     fun startGroup() {
         if (emptyCount <= 0) {
+            val parent = parent
+            val currentGroup = currentGroup
             require(groups.parentAnchor(currentGroup) == parent) { "Invalid slot table detected" }
-            parent = currentGroup
+            sourceInformationMap?.get(anchor(parent))?.reportGroup(table, currentGroup)
+            this.parent = currentGroup
             currentEnd = currentGroup + groups.groupSize(currentGroup)
-            val current = currentGroup++
-            currentSlot = groups.slotAnchor(current)
-            currentSlotEnd = if (current >= groupsSize - 1)
+            this.currentGroup = currentGroup + 1
+            currentSlot = groups.slotAnchor(currentGroup)
+            currentSlotEnd = if (currentGroup >= groupsSize - 1)
                 slotsSize else
-                groups.dataAnchor(current + 1)
+                groups.dataAnchor(currentGroup + 1)
         }
     }
 
@@ -1023,6 +1206,20 @@
         }
     }
 
+    fun recordGroupSourceInformation(sourceInformation: String) {
+        val map = sourceInformationMap ?: HashMap()
+        this.sourceInformationMap = map
+        map[anchor(parent)] = GroupSourceInformation(0, sourceInformation)
+    }
+
+    fun recordGrouplessCallSourceInformationStart(key: Int, sourceInformation: String) {
+        sourceInformationMap?.get(tryAnchor(parent))?.startGrouplessCall(key, sourceInformation)
+    }
+
+    fun recordGrouplessCallSourceInformationEnd() {
+        sourceInformationMap?.get(tryAnchor(parent))?.endGrouplessCall()
+    }
+
     /**
      * Extract the keys from this point to the end of the group. The current is left unaffected.
      * Must be called inside a group.
@@ -1057,6 +1254,11 @@
         Anchor(index)
     }
 
+    /**
+     * Return an anchor if one has already been created, null otherwise.
+     */
+    private fun tryAnchor(index: Int) = table.anchors.find(index, groupsSize)
+
     private fun IntArray.node(index: Int) = if (isNode(index)) {
         slots[nodeIndex(index)]
     } else Composer.Empty
@@ -1126,11 +1328,16 @@
     private var slots: Array<Any?> = table.slots
 
     /**
-     * A copy of the [SlotWriter.anchors] to avoid having to index through [table].
+     * A copy of the [SlotTable.anchors] to avoid having to index through [table].
      */
     private var anchors: ArrayList<Anchor> = table.anchors
 
     /**
+     * A copy of [SlotTable.sourceInformationMap] to avoid having to index through [table]
+     */
+    private var sourceInformationMap = table.sourceInformationMap
+
+    /**
      * Group index of the start of the gap in the groups array.
      */
     private var groupGapStart: Int = table.groupsSize
@@ -1340,7 +1547,8 @@
             groupsSize = groupGapStart,
             slots = slots,
             slotsSize = slotsGapStart,
-            anchors = anchors
+            anchors = anchors,
+            sourceInformationMap = sourceInformationMap,
         )
     }
 
@@ -1411,6 +1619,49 @@
         currentSlot++
     }
 
+    fun recordGroupSourceInformation(sourceInformation: String) {
+        if (insertCount > 0) {
+            groupSourceInformationFor(parent, sourceInformation)
+        }
+    }
+
+    fun recordGrouplessCallSourceInformationStart(key: Int, value: String) {
+        if (insertCount > 0) {
+            groupSourceInformationFor(parent, null)?.startGrouplessCall(key, value)
+        }
+    }
+
+    fun recordGrouplessCallSourceInformationEnd() {
+        if (insertCount > 0) {
+            groupSourceInformationFor(parent, null)?.endGrouplessCall()
+        }
+    }
+
+    private fun groupSourceInformationFor(
+        parent: Int,
+        sourceInformation: String?
+    ): GroupSourceInformation? {
+        val map = sourceInformationMap ?: HashMap()
+        this.sourceInformationMap = map
+        return map.getOrPut(anchor(parent)) {
+            val result = GroupSourceInformation(0, sourceInformation)
+
+            // If we called from a groupless call then the groups added before this call
+            // are not reflected in this group information so they need to be added now
+            // if they exist.
+            if (sourceInformation == null) {
+                var child = parent + 1
+                val end = currentGroup
+                while (child < end) {
+                    result.reportGroup(this, child)
+                    child += groups.groupSize(child)
+                }
+            }
+
+            result
+        }
+    }
+
     /**
      * Updates the node for the current node group to [value].
      */
@@ -1601,6 +1852,7 @@
     fun startData(key: Int, aux: Any?) = startGroup(key, Composer.Empty, isNode = false, aux = aux)
 
     private fun startGroup(key: Int, objectKey: Any?, isNode: Boolean, aux: Any?) {
+        val previousParent = parent
         val inserting = insertCount > 0
         nodeCountStack.push(nodeCount)
 
@@ -1637,9 +1889,11 @@
             val newCurrent = current + 1
             this.parent = current
             this.currentGroup = newCurrent
+            if (previousParent >= 0) {
+                sourceInformationOf(previousParent)?.reportGroup(this, current)
+            }
             newCurrent
         } else {
-            val previousParent = parent
             startStack.push(previousParent)
             saveCurrentGroupEnd()
             val currentGroup = currentGroup
@@ -1740,7 +1994,7 @@
     internal fun bashGroup() {
         startGroup()
         while (!isGroupEnd) {
-            insertParentGroup(-3)
+            bashParentGroup()
             skipGroup()
         }
         endGroup()
@@ -1799,6 +2053,13 @@
         val oldSlot = currentSlot
         val count = skipGroup()
 
+        // Remove the group from its parent information
+        sourceInformationOf(parent)?.let { sourceInformation ->
+            tryAnchor(oldGroup)?.let { anchor ->
+                sourceInformation.removeAnchor(anchor)
+            }
+        }
+
         // Remove any recalculate markers ahead of this delete as they are in the group
         // that is being deleted.
         pendingRecalculateMarks?.let {
@@ -2080,6 +2341,42 @@
                 anchors
             } else emptyList()
 
+            // Move any source information from the source table to the destination table
+            if (anchors.isNotEmpty()) {
+                val sourceSourceInformationMap = fromWriter.sourceInformationMap
+                if (sourceSourceInformationMap != null) {
+                    var destinationSourceInformation = toWriter.sourceInformationMap
+                    anchors.fastForEach { anchor ->
+                        val information = sourceSourceInformationMap[anchor]
+                        if (information != null) {
+                            sourceSourceInformationMap.remove(anchor)
+                            val map = destinationSourceInformation ?: run {
+                                val map = HashMap<Anchor, GroupSourceInformation>()
+                                destinationSourceInformation = map
+                                toWriter.sourceInformationMap = destinationSourceInformation
+                                map
+                            }
+                            map[anchor] = information
+                        }
+                    }
+                    if (sourceSourceInformationMap.isEmpty()) {
+                        fromWriter.sourceInformationMap = null
+                    }
+                }
+            }
+
+            // Record the new group in the parent information
+            val toWriterParent = toWriter.parent
+            toWriter.sourceInformationOf(parent)?.let {
+                var predecessor = -1
+                var child = toWriterParent + 1
+                val endGroup = toWriter.currentGroup
+                while (child < endGroup) {
+                    predecessor = child
+                    child += toWriter.groups.groupSize(child)
+                }
+                it.addGroupAfter(toWriter, predecessor, endGroup)
+            }
             val parentGroup = fromWriter.parent(fromIndex)
             val anchorsRemoved = if (!removeSourceGroup) {
                 // e.g.: we can skip groups removal for insertTable of Composer because
@@ -2131,6 +2428,7 @@
             if (hasMarks) {
                 toWriter.updateContainsMark(parent)
             }
+
             return anchors
         }
     }
@@ -2205,10 +2503,12 @@
             val myGroups = groups
             val mySlots = slots
             val myAnchors = anchors
+            val mySourceInformation = sourceInformationMap
             val groups = table.groups
             val groupsSize = table.groupsSize
             val slots = table.slots
             val slotsSize = table.slotsSize
+            val sourceInformation = table.sourceInformationMap
             this.groups = groups
             this.slots = slots
             this.anchors = table.anchors
@@ -2217,8 +2517,9 @@
             this.slotsGapStart = slotsSize
             this.slotsGapLen = slots.size - slotsSize
             this.slotsGapOwner = groupsSize
+            this.sourceInformationMap = sourceInformation
 
-            table.setTo(myGroups, 0, mySlots, 0, myAnchors)
+            table.setTo(myGroups, 0, mySlots, 0, myAnchors, mySourceInformation)
             return this.anchors
         }
 
@@ -2239,7 +2540,8 @@
      * all remaining children of the current group will be parented by a new group and the
      * [currentSlot] will be moved to after the group inserted.
      */
-    fun insertParentGroup(key: Int) {
+    private fun bashParentGroup() {
+        val key = -3
         runtimeCheck(insertCount == 0) { "Writer cannot be inserting" }
         if (isGroupEnd) {
             beginInsert()
@@ -2282,6 +2584,11 @@
             addToGroupSizeAlongSpine(parentAddress, 1)
             fixParentAnchorsFor(parent, currentGroupEnd, currentGroup)
             this.currentGroup = currentGroupEnd
+
+            // Remove any source information for child groups in the bashed group as updating it
+            // will not work as the children are now separated by a group. Just clearing the list
+            // is sufficient as list will be rebuilt when the new content is generated.
+            sourceInformationOf(parent)?.let { group -> group.groups = null }
         }
     }
 
@@ -2693,7 +3000,9 @@
 
             // Move the gap to start of the removal and grow the gap
             moveGroupGapTo(start)
-            if (anchors.isNotEmpty()) anchorsRemoved = removeAnchors(start, len)
+            if (anchors.isNotEmpty()) {
+                anchorsRemoved = removeAnchors(start, len, sourceInformationMap)
+            }
             groupGapStart = start
             val previousGapLen = groupGapLen
             val newGapLen = previousGapLen + len
@@ -2707,14 +3016,25 @@
             }
             if (currentGroupEnd >= groupGapStart) currentGroupEnd -= len
 
+            val parent = parent
             // Update markers if necessary
             if (containsGroupMark(parent)) {
                 updateContainsMark(parent)
             }
+
+            // Remove the group from its parent source information
             anchorsRemoved
         } else false
     }
 
+    private fun sourceInformationOf(group: Int): GroupSourceInformation? =
+        sourceInformationMap?.let { map ->
+            tryAnchor(group)?.let { anchor -> map[anchor] }
+        }
+
+    internal fun tryAnchor(group: Int) =
+        if (group in 0 until size) anchors.find(group, size) else null
+
     /**
      * Remove [len] slots from [start].
      */
@@ -2782,7 +3102,11 @@
     /**
      * A helper function to remove the anchors for groups that are removed.
      */
-    private fun removeAnchors(gapStart: Int, size: Int): Boolean {
+    private fun removeAnchors(
+        gapStart: Int,
+        size: Int,
+        sourceInformationMap: HashMap<Anchor, GroupSourceInformation>?
+    ): Boolean {
         val gapLen = groupGapLen
         val removeEnd = gapStart + size
         val groupsSize = capacity - gapLen
@@ -2797,6 +3121,7 @@
             if (location >= gapStart) {
                 if (location < removeEnd) {
                     anchor.location = Int.MIN_VALUE
+                    sourceInformationMap?.remove(anchor)
                     removeAnchorStart = index
                     if (removeAnchorEnd == 0) removeAnchorEnd = index + 1
                 }
@@ -3037,7 +3362,9 @@
     override val sourceInfo: String?
         get() = if (table.groups.hasAux(group))
             table.slots[table.groups.auxIndex(group)] as? String
-        else null
+        else table.tryAnchor(group)?.let {
+            table.sourceInformationMap?.get(it)?.sourceInformation
+        }
 
     override val node: Any?
         get() = if (table.groups.isNode(group))
@@ -3056,11 +3383,12 @@
 
     override fun iterator(): Iterator<CompositionGroup> {
         validateRead()
-        return GroupIterator(
-            table,
-            group + 1,
-            group + table.groups.groupSize(group)
-        )
+        return table.sourceInformationOf(group)?.let { SourceInformationGroupIterator(table, it) }
+            ?: GroupIterator(
+                table,
+                group + 1,
+                group + table.groups.groupSize(group)
+            )
     }
 
     override val groupSize: Int get() = table.groups.groupSize(group)
@@ -3090,6 +3418,21 @@
         }
 }
 
+private class SourceInformationSlotTableGroup(
+    val table: SlotTable,
+    val sourceInformation: GroupSourceInformation
+) : CompositionGroup, Iterable<CompositionGroup> {
+    override val key: Any = sourceInformation.key
+    override val sourceInfo: String? get() = sourceInformation.sourceInformation
+    override val node: Any? get() = null
+    override val data: Iterable<Any?> = emptyList()
+    override val compositionGroups: Iterable<CompositionGroup> = this
+    override val isEmpty: Boolean
+        get() = sourceInformation.groups?.isEmpty() != false
+    override fun iterator(): Iterator<CompositionGroup> =
+        SourceInformationGroupIterator(table, sourceInformation)
+}
+
 private class GroupIterator(
     val table: SlotTable,
     start: Int,
@@ -3121,7 +3464,7 @@
 
 private class DataIterator(
     val table: SlotTable,
-    val group: Int,
+    group: Int,
 ) : Iterable<Any?>, Iterator<Any?> {
     val start = table.groups.dataAnchor(group)
     val end = if (group + 1 < table.groupsSize)
@@ -3136,6 +3479,22 @@
     ).also { index++ }
 }
 
+private class SourceInformationGroupIterator(
+    val table: SlotTable,
+    val group: GroupSourceInformation,
+) : Iterator<CompositionGroup> {
+    private val version = table.version
+    private var index = 0
+    override fun hasNext(): Boolean = group.groups?.let { index < it.size } ?: false
+    override fun next(): CompositionGroup {
+        return when (val group = group.groups?.get(index++)) {
+            is Anchor -> SlotTableGroup(table, group.location, version)
+            is GroupSourceInformation -> SourceInformationSlotTableGroup(table, group)
+            else -> composeRuntimeError("Unexpected group information structure")
+        }
+    }
+}
+
 // Parent -1 is reserved to be the root parent index so the anchor must pivot on -2.
 private const val parentAnchorPivot = -2
 
@@ -3364,6 +3723,11 @@
     } else get(location)
 }
 
+private fun ArrayList<Anchor>.find(index: Int, effectiveSize: Int): Anchor? {
+    val location = search(index, effectiveSize)
+    return if (location >= 0) get(location) else null
+}
+
 /**
  * This is inlined here instead to avoid allocating a lambda for the compare when this is used.
  */
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionAndDerivedStateTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionAndDerivedStateTests.kt
index 0ed6a87..dd8461b 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionAndDerivedStateTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionAndDerivedStateTests.kt
@@ -252,6 +252,8 @@
             }
         }
 
+        verifyConsistent()
+
         expect(useD)
 
         // Modify A
@@ -265,6 +267,7 @@
             use = newUse
             a++
             expectChanges()
+            verifyConsistent()
             revalidate()
             expect(newUse, previous)
         }
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
index 8f8615b..d93db8a 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionTests.kt
@@ -3424,13 +3424,17 @@
     @Test // regression test for 264467571
     fun test_returnConditionally_fromNodeLambda_local_initial_return() = compositionTest {
         var condition by mutableStateOf(true)
+
         compose {
+            currentComposer.disableSourceInformation()
             Text("Before outer")
             InlineLinear {
                 Text("Before inner")
                 InlineLinear inner@{
                     Text("Before return")
-                    if (condition) return@inner
+                    if (condition) {
+                        return@inner
+                    }
                     Text("After return")
                 }
                 Text("After inner")
@@ -3463,6 +3467,7 @@
     fun test_returnConditionally_fromNodeLambda_local_initial_no_return() = compositionTest {
         var condition by mutableStateOf(true)
         compose {
+            currentComposer.disableSourceInformation()
             Text("Before outer")
             InlineLinear {
                 Text("Before inner")
@@ -3501,6 +3506,7 @@
     fun test_returnConditionally_fromNodeLambda_nonLocal_initial_return() = compositionTest {
         var condition by mutableStateOf(true)
         compose {
+            currentComposer.disableSourceInformation()
             Text("Before outer")
             InlineLinear outer@{
                 Text("Before inner")
@@ -3539,6 +3545,7 @@
     fun test_returnConditionally_fromNodeLambda_nonLocal_initial_no_return() = compositionTest {
         var condition by mutableStateOf(true)
         compose {
+            currentComposer.disableSourceInformation()
             Text("Before outer")
             InlineLinear outer@{
                 Text("Before inner")
@@ -3578,6 +3585,7 @@
         compositionTest {
             var condition by mutableStateOf(true)
             compose {
+                currentComposer.disableSourceInformation()
                 Text("Before outer")
                 InlineLinear outer@{
                     Text("Before inner")
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/SlotTableTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/SlotTableTests.kt
index 333289b..4fc09ce 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/SlotTableTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/SlotTableTests.kt
@@ -13,10 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-@file:OptIn(InternalComposeApi::class)
 package androidx.compose.runtime
 
+import androidx.compose.runtime.snapshots.fastForEach
+import androidx.compose.runtime.tooling.CompositionData
+import androidx.compose.runtime.tooling.CompositionGroup
 import kotlin.random.Random
 import kotlin.test.Test
 import kotlin.test.assertEquals
@@ -24,7 +25,6 @@
 import kotlin.test.assertSame
 import kotlin.test.assertTrue
 
-@OptIn(InternalComposeApi::class)
 class SlotTableTests {
     @Test
     fun testCanCreate() {
@@ -3910,6 +3910,445 @@
     }
 
     @Test
+    fun canReportNonGroupCallInformationDuringWrite() {
+        val slots = SlotTable()
+        slots.write { writer ->
+            writer.insert {
+                writer.group(100) {
+                    writer.group(200, "C(200)") {
+                        writer.grouplessCall(300, "C(300)") { }
+                        writer.grouplessCall(301, "C(301)") { }
+                        writer.group(302, "C(302)") { }
+                        writer.grouplessCall(303, "C(303)") { }
+                        writer.group(304, "C(304)") { }
+                        writer.grouplessCall(305, "C(305)") {
+                            writer.group(400, "C(400)") { }
+                            writer.group(401, "C(401)") { }
+                        }
+                        writer.grouplessCall(306, "C(306)") {
+                            writer.group(402, "C(402)") { }
+                            writer.grouplessCall(403, "C(403)") {
+                                writer.group(500, "C(500)") { }
+                                writer.group(501, "C(501)") { }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        slots.verifyWellFormed()
+
+        val expectedRoot = SourceGroup.group(100) {
+            group(200, "C(200)") {
+                group(300, "C(300)") { }
+                group(301, "C(301)") { }
+                group(302, "C(302)") { }
+                group(303, "C(303)") { }
+                group(304, "C(304)") { }
+                group(305, "C(305)") {
+                    group(400, "C(400)") { }
+                    group(401, "C(401)") { }
+                }
+                group(306, "C(306)") {
+                    group(402, "C(402)") { }
+                    group(403, "C(403)") {
+                        group(500, "C(500)") { }
+                        group(501, "C(501)") { }
+                    }
+                }
+            }
+        }
+        val slotsRoot = SourceGroup.group(slots)
+        assertEquals(expectedRoot, slotsRoot)
+    }
+
+    @Test
+    fun canMoveSourceInformationFromAnotherTable() {
+        val sourceTable = SlotTable().apply {
+            write { writer ->
+                with(writer) {
+                    insert {
+                        group(200, "C(200)") {
+                            grouplessCall(300, "C(300)") {
+                                group(400, "C(400)") { }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        sourceTable.verifyWellFormed()
+
+        val mainTable = SlotTable().apply {
+            write { writer ->
+                with(writer) {
+                    insert {
+                        group(100) {
+                            group(201, "C(201)") { }
+                        }
+                    }
+                }
+            }
+        }
+        mainTable.verifyWellFormed()
+
+        mainTable.write { writer ->
+            with(writer) {
+                group {
+                    insert {
+                        moveFrom(sourceTable, 0)
+                    }
+                    skipToGroupEnd()
+                }
+            }
+        }
+        mainTable.verifyWellFormed()
+
+        val expected = SourceGroup.group(100) {
+            group(200, "C(200)") {
+                group(300, "C(300)") {
+                    group(400, "C(400)") { }
+                }
+            }
+            group(201, "C(201)") { }
+        }
+        val received = SourceGroup.group(mainTable)
+        assertEquals(expected, received)
+    }
+
+    @Test
+    fun canMoveSourceInformationIntoAGroupWithSourceInformation() {
+        val sourceTable = SlotTable().apply {
+            write { writer ->
+                with(writer) {
+                    insert {
+                        group(300, "C(300)") {
+                            grouplessCall(400, "C(400)") {
+                                group(500, "C(500)") { }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        sourceTable.verifyWellFormed()
+
+        val mainTable = SlotTable().apply {
+            write { writer ->
+                with(writer) {
+                    insert {
+                        group(100) {
+                            group(201, "C(201)") { }
+                        }
+                    }
+                }
+            }
+        }
+        mainTable.verifyWellFormed()
+
+        mainTable.write { writer ->
+            with(writer) {
+                group {
+                    group(201) {
+                        insert {
+                            moveFrom(sourceTable, 0)
+                        }
+                        skipToGroupEnd()
+                    }
+                    skipToGroupEnd()
+                }
+            }
+        }
+        mainTable.verifyWellFormed()
+
+        val expected = SourceGroup.group(100) {
+            group(201, "C(201)") {
+                group(300, "C(300)") {
+                    group(400, "C(400)") {
+                        group(500, "C(500)") { }
+                    }
+                }
+            }
+        }
+        val received = SourceGroup.group(mainTable)
+        assertEquals(expected, received)
+    }
+
+    @Test
+    fun canRemoveAGroupBeforeAnEmptyGrouplessCall() {
+        val slots = SlotTable().apply {
+            write { writer ->
+                with(writer) {
+                    insert {
+                        group(100) {
+                            group(200, "C(2001)") { }
+                            grouplessCall(201, "C(201)") { }
+                            group(202, "C(202)") { }
+                        }
+                    }
+                }
+            }
+        }
+        slots.verifyWellFormed()
+
+        slots.write { writer ->
+            with(writer) {
+                group {
+                    removeGroup()
+                    skipToGroupEnd()
+                }
+            }
+        }
+        slots.verifyWellFormed()
+
+        val expected = SourceGroup.group(100) {
+            group(201, "C(201)") { }
+            group(202, "C(202)") { }
+        }
+        val received = SourceGroup.group(slots)
+        assertEquals(expected, received)
+    }
+
+    @Test
+    fun canRemoveAGroupAfterAnEmptyGrouplessCall() {
+        val slots = SlotTable().apply {
+            write { writer ->
+                with(writer) {
+                    insert {
+                        group(100) {
+                            group(200, "C(200)") { }
+                            grouplessCall(201, "C(201)") { }
+                            group(202, "C(202)") { }
+                        }
+                    }
+                }
+            }
+        }
+        slots.verifyWellFormed()
+
+        slots.write { writer ->
+            with(writer) {
+                group {
+                    skipGroup()
+                    removeGroup()
+                    skipToGroupEnd()
+                }
+            }
+        }
+        slots.verifyWellFormed()
+
+        val expected = SourceGroup.group(100) {
+            group(200, "C(200)") { }
+            group(201, "C(201)") { }
+        }
+        val received = SourceGroup.group(slots)
+        assertEquals(expected, received)
+    }
+
+    @Test
+    fun canRemoveAGroupProducedInAGrouplessCall() {
+        val slots = SlotTable().apply {
+            write { writer ->
+                with(writer) {
+                    insert {
+                        group(100) {
+                            group(200, "C(200)") { }
+                            grouplessCall(201, "C(201)") {
+                                group(300, "C(300)") { }
+                            }
+                            group(202, "C(202)") { }
+                        }
+                    }
+                }
+            }
+        }
+        slots.verifyWellFormed()
+
+        slots.write { writer ->
+            with(writer) {
+                group {
+                    skipGroup()
+                    removeGroup()
+                    skipToGroupEnd()
+                }
+            }
+        }
+        slots.verifyWellFormed()
+
+        val expected = SourceGroup.group(100) {
+            group(200, "C(200)") { }
+            group(202, "C(202)") { }
+        }
+        val received = SourceGroup.group(slots)
+        assertEquals(expected, received)
+    }
+
+    @Test
+    fun canRemoveAGroupWithSourceInformation() {
+        val slots = SlotTable().apply {
+            write { writer ->
+                with(writer) {
+                    insert {
+                        group(100) {
+                            group(200, "C(200)") { }
+                            group(201, "C(201)") { }
+                            group(202, "C(202)") {
+                                grouplessCall(300, "C(300)") {
+                                    group(400, "C(400)") {
+                                        group(500, "C(500)") { }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        slots.verifyWellFormed()
+
+        slots.write { writer ->
+            with(writer) {
+                group(100) {
+                    skipGroup()
+                    skipGroup()
+                    group {
+                        group {
+                            removeGroup() // Remove group 500
+                            skipToGroupEnd()
+                        }
+                    }
+                }
+            }
+        }
+        slots.verifyWellFormed()
+
+        val expected = SourceGroup.group(100) {
+            group(200, "C(200)") { }
+            group(201, "C(201)") { }
+            group(202, "C(202)") {
+                group(300, "C(300)") {
+                    group(400, "C(400)") { }
+                }
+            }
+        }
+        val received = SourceGroup.group(slots)
+
+        assertEquals(expected, received)
+    }
+
+    @Test
+    fun canAddSourceInformationUsingAReader() {
+        val slots = SlotTable().apply {
+            write { writer ->
+                with(writer) {
+                    insert {
+                        group(100) {
+                            group(200) { }
+                            group(201) { }
+                            group(202) {
+                                group(400) {
+                                    group(500) { }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        slots.verifyWellFormed()
+
+        val initialRootActual = SourceGroup.group(slots)
+        val initialRootExpected = SourceGroup.group(100) {
+            group(200) { }
+            group(201) { }
+            group(202) {
+                group(400) {
+                    group(500) { }
+                }
+            }
+        }
+        assertEquals(initialRootExpected, initialRootActual)
+
+        slots.read { reader ->
+            with(reader) {
+                group(100) {
+                    group(200, "C(200)") { }
+                    group(201, "C(201)") { }
+                    group(202, "C(202)") {
+                        grouplessCall(300, "C(300)") {
+                            group(400, "C(400)") {
+                                group(500, "C(500)") { }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        val updatedRootActual = SourceGroup.group(slots)
+        val updatedRootExpected = SourceGroup.group(100) {
+            group(200, "C(200)") { }
+            group(201, "C(201)") { }
+            group(202, "C(202)") {
+                group(300, "C(300)") {
+                    group(400, "C(400)") {
+                        group(500, "C(500)") { }
+                    }
+                }
+            }
+        }
+        assertEquals(updatedRootExpected, updatedRootActual)
+    }
+
+    @Test
+    fun canAddAGrouplessCallToAGroupWithNoSourceInformation() {
+        val slots = SlotTable().apply {
+            write { writer ->
+                with(writer) {
+                    insert {
+                        group(100) {
+                            group(200) {
+                                group(300, "C(300)") { }
+                                group(301, "C(301)") { }
+                                grouplessCall(302, "C(302)") {
+                                    group(400, "C(400)") { }
+                                }
+                            }
+                            group(201, "C(201)") {
+                                group(303) {
+                                    group(401, "C(401)") { }
+                                    grouplessCall(402, "C(402)") { }
+                                    group(403, "C(403)") { }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        val expected = SourceGroup.group(100) {
+            group(200) {
+                group(300, "C(300)") { }
+                group(301, "C(301)") { }
+                group(302, "C(302)") {
+                    group(400, "C(400)") { }
+                }
+            }
+            group(201, "C(201)") {
+                group(303) {
+                    group(401, "C(401)") { }
+                    group(402, "C(402)") { }
+                    group(403, "C(403)") { }
+                }
+            }
+        }
+        val received = SourceGroup.group(slots)
+
+        assertEquals(expected, received)
+    }
+
+    @Test
     fun canMoveAGroupFromATableIntoAnotherGroupAndModifyThatGroup() {
         val slots = SlotTable()
         var insertAnchor = Anchor(-1)
@@ -4013,34 +4452,47 @@
 private fun SlotWriter.startNode(key: Any?, node: Any?) =
     startNode(NodeKey, key, node)
 
-@OptIn(InternalComposeApi::class)
 internal inline fun SlotWriter.group(block: () -> Unit) {
     startGroup()
     block()
     endGroup()
 }
 
-@OptIn(InternalComposeApi::class)
 internal inline fun SlotWriter.group(key: Int, block: () -> Unit) {
     startGroup(key)
     block()
     endGroup()
 }
 
-@OptIn(InternalComposeApi::class)
+internal inline fun SlotWriter.group(key: Int, sourceInformation: String, block: () -> Unit) {
+    group(key) {
+        recordGroupSourceInformation(sourceInformation)
+        block()
+    }
+}
+
+internal inline fun SlotWriter.grouplessCall(
+    key: Int,
+    sourceInformation: String,
+    block: () -> Unit
+) {
+    recordGrouplessCallSourceInformationStart(key, sourceInformation)
+    block()
+    recordGrouplessCallSourceInformationEnd()
+}
+
 internal inline fun SlotWriter.nodeGroup(key: Int, node: Any, block: () -> Unit = { }) {
     startNode(NodeKey, key, node)
     block()
     endGroup()
 }
-@OptIn(InternalComposeApi::class)
+
 internal inline fun SlotWriter.insert(block: () -> Unit) {
     beginInsert()
     block()
     endInsert()
 }
 
-@OptIn(InternalComposeApi::class)
 internal inline fun SlotReader.group(key: Int, block: () -> Unit) {
     assertEquals(key, groupKey)
     startGroup()
@@ -4048,14 +4500,30 @@
     endGroup()
 }
 
-@OptIn(InternalComposeApi::class)
+internal inline fun SlotReader.group(key: Int, sourceInformation: String, block: () -> Unit) {
+    assertEquals(key, groupKey)
+    startGroup()
+    recordGroupSourceInformation(sourceInformation)
+    block()
+    endGroup()
+}
+
+internal inline fun SlotReader.grouplessCall(
+    key: Int,
+    sourceInformation: String,
+    block: () -> Unit
+) {
+    recordGrouplessCallSourceInformationStart(key, sourceInformation)
+    block()
+    recordGrouplessCallSourceInformationEnd()
+}
+
 internal inline fun SlotReader.group(block: () -> Unit) {
     startGroup()
     block()
     endGroup()
 }
 
-@OptIn(InternalComposeApi::class)
 private inline fun SlotReader.expectNode(key: Int, node: Any, block: () -> Unit = { }) {
     assertEquals(key, groupObjectKey)
     assertEquals(node, groupNode)
@@ -4067,7 +4535,6 @@
 private const val treeRoot = -1
 private const val elementKey = 100
 
-@OptIn(InternalComposeApi::class)
 private fun testSlotsNumbered(): SlotTable {
     val slotTable = SlotTable()
     slotTable.write { writer ->
@@ -4084,7 +4551,6 @@
 }
 
 // Creates 0 until 10 items each with 10 elements numbered 0...n with 0..n slots
-@OptIn(InternalComposeApi::class)
 private fun testItems(): SlotTable {
     val slots = SlotTable()
     slots.write { writer ->
@@ -4121,7 +4587,6 @@
     return slots
 }
 
-@OptIn(InternalComposeApi::class)
 private fun validateItems(slots: SlotTable) {
     slots.read { reader ->
         check(reader.groupKey == treeRoot) { "Invalid root key" }
@@ -4172,7 +4637,6 @@
     }
 }
 
-@OptIn(InternalComposeApi::class)
 private fun narrowTrees(): Pair<SlotTable, List<Anchor>> {
     val slots = SlotTable()
     val anchors = mutableListOf<Anchor>()
@@ -4221,13 +4685,11 @@
     return slots to anchors
 }
 
-@OptIn(InternalComposeApi::class)
 private fun SlotReader.expectGroup(key: Int): Int {
     assertEquals(key, groupKey)
     return skipGroup()
 }
 
-@OptIn(InternalComposeApi::class)
 private fun SlotReader.expectGroup(
     key: Int,
     block: () -> Unit
@@ -4238,12 +4700,10 @@
     endGroup()
 }
 
-@OptIn(InternalComposeApi::class)
 private fun SlotReader.expectData(value: Any) {
     assertEquals(value, next())
 }
 
-@OptIn(InternalComposeApi::class)
 private fun SlotReader.expectGroup(
     key: Int,
     objectKey: Any?,
@@ -4280,3 +4740,43 @@
         "Expected test to throw an exception containing \"$message\""
     )
 }
+
+data class SourceGroup(val key: Any, val source: String?, val children: List<SourceGroup>) {
+
+    override fun toString(): String = buildString { toStringBuilder(this, 0) }
+
+    private fun toStringBuilder(builder: StringBuilder, indent: Int) {
+        repeat(indent) { builder.append(' ') }
+        builder.append("Group(")
+        builder.append(key)
+        builder.append(")")
+        if (source != null) {
+            builder.append(' ')
+            builder.append(source)
+        }
+        builder.appendLine()
+        children.fastForEach { it.toStringBuilder(builder, indent + 2) }
+    }
+
+    data class BuilderScope(private val children: ArrayList<SourceGroup> = ArrayList()) {
+        fun group(key: Int, source: String? = null, block: BuilderScope.() -> Unit) {
+            val scope = BuilderScope()
+            scope.block()
+            this.children.add(SourceGroup(key, source, scope.children))
+        }
+    }
+
+    companion object {
+        fun group(key: Int, block: BuilderScope.() -> Unit): SourceGroup {
+            val children = ArrayList<SourceGroup>()
+            val scope = BuilderScope(children)
+            scope.block()
+            return SourceGroup(key, null, children)
+        }
+
+        fun group(compositionData: CompositionData): SourceGroup =
+            groupOf(compositionData.compositionGroups.first())
+        private fun groupOf(group: CompositionGroup): SourceGroup =
+            SourceGroup(group.key, group.sourceInfo, group.compositionGroups.map(::groupOf))
+    }
+}
diff --git a/compose/runtime/runtime/src/nonEmulatorJvmTest/kotlin/androidx/compose/runtime/LiveEditTests.kt b/compose/runtime/runtime/src/nonEmulatorJvmTest/kotlin/androidx/compose/runtime/LiveEditTests.kt
index dcb954f..7fc505e 100644
--- a/compose/runtime/runtime/src/nonEmulatorJvmTest/kotlin/androidx/compose/runtime/LiveEditTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorJvmTest/kotlin/androidx/compose/runtime/LiveEditTests.kt
@@ -102,7 +102,9 @@
     }
 
     @Test
-    fun testNonRestartableFunctionPreservesParentAndSiblingState() = liveEditTest {
+    fun testNonRestartableFunctionPreservesParentAndSiblingState() = liveEditTest(
+        collectSourceInformation = SourceInfo.None
+    ) {
         EnsureStatePreservedButRecomposed("a")
         RestartGroup {
             Text("Hello World")
@@ -112,7 +114,9 @@
     }
 
     @Test
-    fun testMultipleNonRestartableFunctionPreservesParentAndSiblingState() = liveEditTest {
+    fun testMultipleNonRestartableFunctionPreservesParentAndSiblingState() = liveEditTest(
+        collectSourceInformation = SourceInfo.None
+    ) {
         RestartGroup {
             EnsureStatePreservedButRecomposed("a")
             Target("b", restartable = false)
@@ -136,7 +140,9 @@
     }
 
     @Test
-    fun testInlineComposableLambda() = liveEditTest {
+    fun testInlineComposableLambda() = liveEditTest(
+        collectSourceInformation = SourceInfo.None
+    ) {
         RestartGroup {
             InlineTarget("a")
             EnsureStatePreservedButRecomposed("b")
@@ -165,7 +171,10 @@
     @Test
     fun testThrowing_recomposition() {
         var recomposeCount = 0
-        liveEditTest(reloadCount = 2) {
+        liveEditTest(
+            reloadCount = 2,
+            collectSourceInformation = SourceInfo.None,
+        ) {
             RestartGroup {
                 MarkAsTarget()
 
@@ -216,7 +225,9 @@
     @Test
     fun testThrowing_recomposition_sideEffect() {
         var recomposeCount = 0
-        liveEditTest {
+        liveEditTest(
+            collectSourceInformation = SourceInfo.None
+        ) {
             RestartGroup {
                 MarkAsTarget()
 
@@ -286,7 +297,9 @@
     @Test
     fun testThrowing_recomposition_remembered() {
         var recomposeCount = 0
-        liveEditTest {
+        liveEditTest(
+            collectSourceInformation = SourceInfo.None,
+        ) {
             RestartGroup {
                 MarkAsTarget()
 
@@ -333,7 +346,10 @@
     fun testThrowing_invalidationsCarriedAfterCrash() {
         var recomposeCount = 0
         val state = mutableStateOf(0)
-        liveEditTest(reloadCount = 2) {
+        liveEditTest(
+            reloadCount = 2,
+            collectSourceInformation = SourceInfo.None,
+        ) {
             RestartGroup {
                 RestartGroup {
                     MarkAsTarget()
@@ -391,7 +407,10 @@
     @Test
     fun testThrowing_movableContent_recomposition() {
         var recomposeCount = 0
-        liveEditTest(reloadCount = 2) {
+        liveEditTest(
+            reloadCount = 2,
+            collectSourceInformation = SourceInfo.None,
+        ) {
             RestartGroup {
                 MarkAsTarget()
 
@@ -423,7 +442,10 @@
     @Test
     fun testThrowing_movableContent_throwAfterMove() {
         var recomposeCount = 0
-        liveEditTest(reloadCount = 2) {
+        liveEditTest(
+            reloadCount = 2,
+            collectSourceInformation = SourceInfo.None,
+        ) {
             expectError("throwInMovableContent", 1)
 
             val content = remember {
@@ -567,28 +589,71 @@
     addTargetKey((currentComposer as ComposerImpl).parentKey())
 }
 
+enum class SourceInfo {
+    None,
+    Collect,
+    Both,
+}
+
 @OptIn(InternalComposeApi::class)
 fun liveEditTest(
     reloadCount: Int = 1,
+    collectSourceInformation: SourceInfo = SourceInfo.Both,
     fn: @Composable LiveEditTestScope.() -> Unit,
-) = compositionTest {
-    with(LiveEditTestScope()) {
-        addCheck {
-            (composition as? ControlledComposition)?.verifyConsistent()
-        }
+) {
+    if (
+        collectSourceInformation == SourceInfo.Both ||
+        collectSourceInformation == SourceInfo.Collect
+    ) {
+        compositionTest {
+            with(LiveEditTestScope()) {
+                addCheck {
+                    (composition as? ControlledComposition)?.verifyConsistent()
+                }
 
-        recordErrors {
-            compose { fn(this) }
-        }
+                recordErrors {
+                    compose {
+                        currentComposer.collectParameterInformation()
+                        fn(this)
+                    }
+                }
 
-        repeat(reloadCount) {
-            invalidateTargets()
-            recordErrors {
-                advance()
+                repeat(reloadCount) {
+                    invalidateTargets()
+                    recordErrors {
+                        advance()
+                    }
+                }
+
+                runChecks()
             }
         }
+    }
 
-        runChecks()
+    if (
+        collectSourceInformation == SourceInfo.Both ||
+        collectSourceInformation == SourceInfo.None
+    ) {
+        compositionTest {
+            with(LiveEditTestScope()) {
+                addCheck {
+                    (composition as? ControlledComposition)?.verifyConsistent()
+                }
+
+                recordErrors {
+                    compose { fn(this) }
+                }
+
+                repeat(reloadCount) {
+                    invalidateTargets()
+                    recordErrors {
+                        advance()
+                    }
+                }
+
+                runChecks()
+            }
+        }
     }
 }
 
diff --git a/compose/ui/ui-graphics/api/current.txt b/compose/ui/ui-graphics/api/current.txt
index 66bd67f..9416825 100644
--- a/compose/ui/ui-graphics/api/current.txt
+++ b/compose/ui/ui-graphics/api/current.txt
@@ -378,16 +378,16 @@
 
   public final class ColorKt {
     method @androidx.compose.runtime.Stable public static long Color(float red, float green, float blue, optional float alpha, optional androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace);
-    method @androidx.compose.runtime.Stable public static long Color(@ColorInt int color);
-    method @androidx.compose.runtime.Stable public static long Color(@IntRange(from=0L, to=255L) int red, @IntRange(from=0L, to=255L) int green, @IntRange(from=0L, to=255L) int blue, optional @IntRange(from=0L, to=255L) int alpha);
+    method @androidx.compose.runtime.Stable public static long Color(int color);
+    method @androidx.compose.runtime.Stable public static long Color(int red, int green, int blue, optional int alpha);
     method @androidx.compose.runtime.Stable public static long Color(long color);
     method @androidx.compose.runtime.Stable public static long compositeOver(long, long background);
     method public static inline boolean isSpecified(long);
     method public static inline boolean isUnspecified(long);
-    method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, @FloatRange(from=0.0, to=1.0) float fraction);
+    method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method @androidx.compose.runtime.Stable public static float luminance(long);
     method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.graphics.Color> block);
-    method @ColorInt @androidx.compose.runtime.Stable public static int toArgb(long);
+    method @androidx.compose.runtime.Stable public static int toArgb(long);
   }
 
   @kotlin.jvm.JvmInline public final value class ColorMatrix {
@@ -569,8 +569,8 @@
   public final class OutlineKt {
     method public static void addOutline(androidx.compose.ui.graphics.Path, androidx.compose.ui.graphics.Outline outline);
     method public static void drawOutline(androidx.compose.ui.graphics.Canvas, androidx.compose.ui.graphics.Outline outline, androidx.compose.ui.graphics.Paint paint);
-    method public static void drawOutline(androidx.compose.ui.graphics.drawscope.DrawScope, androidx.compose.ui.graphics.Outline outline, androidx.compose.ui.graphics.Brush brush, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public static void drawOutline(androidx.compose.ui.graphics.drawscope.DrawScope, androidx.compose.ui.graphics.Outline outline, long color, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public static void drawOutline(androidx.compose.ui.graphics.drawscope.DrawScope, androidx.compose.ui.graphics.Outline outline, androidx.compose.ui.graphics.Brush brush, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public static void drawOutline(androidx.compose.ui.graphics.drawscope.DrawScope, androidx.compose.ui.graphics.Outline outline, long color, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
   }
 
   public interface Paint {
@@ -728,7 +728,7 @@
 
   public final class PixelMap {
     ctor public PixelMap(int[] buffer, int width, int height, int bufferOffset, int stride);
-    method public operator long get(@IntRange(from=0L) int x, @IntRange(from=0L) int y);
+    method public operator long get(int x, int y);
     method public int[] getBuffer();
     method public int getBufferOffset();
     method public int getHeight();
@@ -931,8 +931,8 @@
   }
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class ColorModel {
-    method @IntRange(from=1L, to=4L) public int getComponentCount();
-    property @IntRange(from=1L, to=4L) @androidx.compose.runtime.Stable public final int componentCount;
+    method public int getComponentCount();
+    property @androidx.compose.runtime.Stable public final int componentCount;
     field public static final androidx.compose.ui.graphics.colorspace.ColorModel.Companion Companion;
   }
 
@@ -949,18 +949,18 @@
 
   public abstract class ColorSpace {
     ctor public ColorSpace(String name, long model);
-    method @Size(min=3L) public final float[] fromXyz(float x, float y, float z);
-    method @Size(min=3L) public abstract float[] fromXyz(@Size(min=3L) float[] v);
-    method @IntRange(from=1L, to=4L) public final int getComponentCount();
-    method public abstract float getMaxValue(@IntRange(from=0L, to=3L) int component);
-    method public abstract float getMinValue(@IntRange(from=0L, to=3L) int component);
+    method public final float[] fromXyz(float x, float y, float z);
+    method public abstract float[] fromXyz(float[] v);
+    method public final int getComponentCount();
+    method public abstract float getMaxValue(int component);
+    method public abstract float getMinValue(int component);
     method public final long getModel();
     method public final String getName();
     method public boolean isSrgb();
     method public abstract boolean isWideGamut();
-    method @Size(3L) public final float[] toXyz(float r, float g, float b);
-    method @Size(min=3L) public abstract float[] toXyz(@Size(min=3L) float[] v);
-    property @IntRange(from=1L, to=4L) public final int componentCount;
+    method public final float[] toXyz(float r, float g, float b);
+    method public abstract float[] toXyz(float[] v);
+    property public final int componentCount;
     property public boolean isSrgb;
     property public abstract boolean isWideGamut;
     property public final long model;
@@ -991,7 +991,7 @@
     method public androidx.compose.ui.graphics.colorspace.Rgb getProPhotoRgb();
     method public androidx.compose.ui.graphics.colorspace.Rgb getSmpteC();
     method public androidx.compose.ui.graphics.colorspace.Rgb getSrgb();
-    method public androidx.compose.ui.graphics.colorspace.ColorSpace? match(@Size(9L) float[] toXYZD50, androidx.compose.ui.graphics.colorspace.TransferParameters function);
+    method public androidx.compose.ui.graphics.colorspace.ColorSpace? match(float[] toXYZD50, androidx.compose.ui.graphics.colorspace.TransferParameters function);
     property public final androidx.compose.ui.graphics.colorspace.Rgb Aces;
     property public final androidx.compose.ui.graphics.colorspace.Rgb Acescg;
     property public final androidx.compose.ui.graphics.colorspace.Rgb AdobeRgb;
@@ -1016,8 +1016,8 @@
     method public final androidx.compose.ui.graphics.colorspace.ColorSpace getDestination();
     method public final int getRenderIntent();
     method public final androidx.compose.ui.graphics.colorspace.ColorSpace getSource();
-    method @Size(3L) public final float[] transform(float r, float g, float b);
-    method @Size(min=3L) public float[] transform(@Size(min=3L) float[] v);
+    method public final float[] transform(float r, float g, float b);
+    method public float[] transform(float[] v);
     property public final androidx.compose.ui.graphics.colorspace.ColorSpace destination;
     property public final int renderIntent;
     property public final androidx.compose.ui.graphics.colorspace.ColorSpace source;
@@ -1061,30 +1061,30 @@
   }
 
   public final class Rgb extends androidx.compose.ui.graphics.colorspace.ColorSpace {
-    ctor public Rgb(@Size(min=1L) String name, @Size(9L) float[] toXYZ, androidx.compose.ui.graphics.colorspace.TransferParameters function);
-    ctor public Rgb(@Size(min=1L) String name, @Size(min=6L, max=9L) float[] primaries, androidx.compose.ui.graphics.colorspace.WhitePoint whitePoint, androidx.compose.ui.graphics.colorspace.TransferParameters function);
-    ctor public Rgb(@Size(min=1L) String name, @Size(min=6L, max=9L) float[] primaries, androidx.compose.ui.graphics.colorspace.WhitePoint whitePoint, double gamma);
-    ctor public Rgb(@Size(min=1L) String name, @Size(min=6L, max=9L) float[] primaries, androidx.compose.ui.graphics.colorspace.WhitePoint whitePoint, kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Double> oetf, kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Double> eotf, float min, float max);
-    ctor public Rgb(@Size(min=1L) String name, @Size(9L) float[] toXYZ, double gamma);
-    ctor public Rgb(@Size(min=1L) String name, @Size(9L) float[] toXYZ, kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Double> oetf, kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Double> eotf);
-    method @Size(3L) public float[] fromLinear(float r, float g, float b);
-    method @Size(min=3L) public float[] fromLinear(@Size(min=3L) float[] v);
+    ctor public Rgb(String name, float[] toXYZ, androidx.compose.ui.graphics.colorspace.TransferParameters function);
+    ctor public Rgb(String name, float[] primaries, androidx.compose.ui.graphics.colorspace.WhitePoint whitePoint, androidx.compose.ui.graphics.colorspace.TransferParameters function);
+    ctor public Rgb(String name, float[] primaries, androidx.compose.ui.graphics.colorspace.WhitePoint whitePoint, double gamma);
+    ctor public Rgb(String name, float[] primaries, androidx.compose.ui.graphics.colorspace.WhitePoint whitePoint, kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Double> oetf, kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Double> eotf, float min, float max);
+    ctor public Rgb(String name, float[] toXYZ, double gamma);
+    ctor public Rgb(String name, float[] toXYZ, kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Double> oetf, kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Double> eotf);
+    method public float[] fromLinear(float r, float g, float b);
+    method public float[] fromLinear(float[] v);
     method public float[] fromXyz(float[] v);
     method public kotlin.jvm.functions.Function1<java.lang.Double,java.lang.Double> getEotf();
-    method @Size(9L) public float[] getInverseTransform();
-    method @Size(min=9L) public float[] getInverseTransform(@Size(min=9L) float[] inverseTransform);
+    method public float[] getInverseTransform();
+    method public float[] getInverseTransform(float[] inverseTransform);
     method public float getMaxValue(int component);
     method public float getMinValue(int component);
     method public kotlin.jvm.functions.Function1<java.lang.Double,java.lang.Double> getOetf();
-    method @Size(6L) public float[] getPrimaries();
-    method @Size(min=6L) public float[] getPrimaries(@Size(min=6L) float[] primaries);
+    method public float[] getPrimaries();
+    method public float[] getPrimaries(float[] primaries);
     method public androidx.compose.ui.graphics.colorspace.TransferParameters? getTransferParameters();
-    method @Size(9L) public float[] getTransform();
-    method @Size(min=9L) public float[] getTransform(@Size(min=9L) float[] transform);
+    method public float[] getTransform();
+    method public float[] getTransform(float[] transform);
     method public androidx.compose.ui.graphics.colorspace.WhitePoint getWhitePoint();
     method public boolean isWideGamut();
-    method @Size(3L) public float[] toLinear(float r, float g, float b);
-    method @Size(min=3L) public float[] toLinear(@Size(min=3L) float[] v);
+    method public float[] toLinear(float r, float g, float b);
+    method public float[] toLinear(float[] v);
     method public float[] toXyz(float[] v);
     property public final kotlin.jvm.functions.Function1<java.lang.Double,java.lang.Double> eotf;
     property public boolean isSrgb;
@@ -1139,24 +1139,24 @@
   public final class CanvasDrawScope implements androidx.compose.ui.graphics.drawscope.DrawScope {
     ctor public CanvasDrawScope();
     method public inline void draw(androidx.compose.ui.unit.Density density, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.ui.graphics.Canvas canvas, long size, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.DrawScope,kotlin.Unit> block);
-    method public void drawArc(androidx.compose.ui.graphics.Brush brush, float startAngle, float sweepAngle, boolean useCenter, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawArc(long color, float startAngle, float sweepAngle, boolean useCenter, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawCircle(androidx.compose.ui.graphics.Brush brush, float radius, long center, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawCircle(long color, float radius, long center, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawImage(androidx.compose.ui.graphics.ImageBitmap image, long topLeft, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method @Deprecated public void drawImage(androidx.compose.ui.graphics.ImageBitmap image, long srcOffset, long srcSize, long dstOffset, long dstSize, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawLine(androidx.compose.ui.graphics.Brush brush, long start, long end, float strokeWidth, int cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawLine(long color, long start, long end, float strokeWidth, int cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawOval(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawOval(long color, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.Brush brush, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawPath(androidx.compose.ui.graphics.Path path, long color, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, int pointMode, androidx.compose.ui.graphics.Brush brush, float strokeWidth, int cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, int pointMode, long color, float strokeWidth, int cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawRect(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawRect(long color, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawRoundRect(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, long cornerRadius, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawRoundRect(long color, long topLeft, long size, long cornerRadius, androidx.compose.ui.graphics.drawscope.DrawStyle style, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawArc(androidx.compose.ui.graphics.Brush brush, float startAngle, float sweepAngle, boolean useCenter, long topLeft, long size, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawArc(long color, float startAngle, float sweepAngle, boolean useCenter, long topLeft, long size, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawCircle(androidx.compose.ui.graphics.Brush brush, float radius, long center, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawCircle(long color, float radius, long center, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawImage(androidx.compose.ui.graphics.ImageBitmap image, long topLeft, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method @Deprecated public void drawImage(androidx.compose.ui.graphics.ImageBitmap image, long srcOffset, long srcSize, long dstOffset, long dstSize, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawLine(androidx.compose.ui.graphics.Brush brush, long start, long end, float strokeWidth, int cap, androidx.compose.ui.graphics.PathEffect? pathEffect, float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawLine(long color, long start, long end, float strokeWidth, int cap, androidx.compose.ui.graphics.PathEffect? pathEffect, float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawOval(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawOval(long color, long topLeft, long size, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.Brush brush, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawPath(androidx.compose.ui.graphics.Path path, long color, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, int pointMode, androidx.compose.ui.graphics.Brush brush, float strokeWidth, int cap, androidx.compose.ui.graphics.PathEffect? pathEffect, float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, int pointMode, long color, float strokeWidth, int cap, androidx.compose.ui.graphics.PathEffect? pathEffect, float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawRect(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawRect(long color, long topLeft, long size, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawRoundRect(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, long cornerRadius, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawRoundRect(long color, long topLeft, long size, long cornerRadius, androidx.compose.ui.graphics.drawscope.DrawStyle style, float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
     method public float getDensity();
     method public androidx.compose.ui.graphics.drawscope.DrawContext getDrawContext();
     method public float getFontScale();
@@ -1189,25 +1189,25 @@
   }
 
   @androidx.compose.ui.graphics.drawscope.DrawScopeMarker @kotlin.jvm.JvmDefaultWithCompatibility public interface DrawScope extends androidx.compose.ui.unit.Density {
-    method public void drawArc(androidx.compose.ui.graphics.Brush brush, float startAngle, float sweepAngle, boolean useCenter, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawArc(long color, float startAngle, float sweepAngle, boolean useCenter, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawCircle(androidx.compose.ui.graphics.Brush brush, optional float radius, optional long center, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawCircle(long color, optional float radius, optional long center, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawImage(androidx.compose.ui.graphics.ImageBitmap image, optional long topLeft, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method @Deprecated public void drawImage(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public default void drawImage(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode, optional int filterQuality);
-    method public void drawLine(androidx.compose.ui.graphics.Brush brush, long start, long end, optional float strokeWidth, optional int cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawLine(long color, long start, long end, optional float strokeWidth, optional int cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawOval(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawOval(long color, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.Brush brush, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawPath(androidx.compose.ui.graphics.Path path, long color, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, int pointMode, androidx.compose.ui.graphics.Brush brush, optional float strokeWidth, optional int cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, int pointMode, long color, optional float strokeWidth, optional int cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawRect(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawRect(long color, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawRoundRect(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional long cornerRadius, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawRoundRect(long color, optional long topLeft, optional long size, optional long cornerRadius, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawArc(androidx.compose.ui.graphics.Brush brush, float startAngle, float sweepAngle, boolean useCenter, optional long topLeft, optional long size, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawArc(long color, float startAngle, float sweepAngle, boolean useCenter, optional long topLeft, optional long size, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawCircle(androidx.compose.ui.graphics.Brush brush, optional float radius, optional long center, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawCircle(long color, optional float radius, optional long center, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawImage(androidx.compose.ui.graphics.ImageBitmap image, optional long topLeft, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method @Deprecated public void drawImage(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public default void drawImage(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode, optional int filterQuality);
+    method public void drawLine(androidx.compose.ui.graphics.Brush brush, long start, long end, optional float strokeWidth, optional int cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawLine(long color, long start, long end, optional float strokeWidth, optional int cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawOval(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawOval(long color, optional long topLeft, optional long size, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.Brush brush, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawPath(androidx.compose.ui.graphics.Path path, long color, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, int pointMode, androidx.compose.ui.graphics.Brush brush, optional float strokeWidth, optional int cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, int pointMode, long color, optional float strokeWidth, optional int cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawRect(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawRect(long color, optional long topLeft, optional long size, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawRoundRect(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional long cornerRadius, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawRoundRect(long color, optional long topLeft, optional long size, optional long cornerRadius, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
     method public default long getCenter();
     method public androidx.compose.ui.graphics.drawscope.DrawContext getDrawContext();
     method public androidx.compose.ui.unit.LayoutDirection getLayoutDirection();
diff --git a/compose/ui/ui-graphics/api/restricted_current.txt b/compose/ui/ui-graphics/api/restricted_current.txt
index ecfd67e..935e1f5 100644
--- a/compose/ui/ui-graphics/api/restricted_current.txt
+++ b/compose/ui/ui-graphics/api/restricted_current.txt
@@ -409,16 +409,16 @@
 
   public final class ColorKt {
     method @androidx.compose.runtime.Stable public static long Color(float red, float green, float blue, optional float alpha, optional androidx.compose.ui.graphics.colorspace.ColorSpace colorSpace);
-    method @androidx.compose.runtime.Stable public static long Color(@ColorInt int color);
-    method @androidx.compose.runtime.Stable public static long Color(@IntRange(from=0L, to=255L) int red, @IntRange(from=0L, to=255L) int green, @IntRange(from=0L, to=255L) int blue, optional @IntRange(from=0L, to=255L) int alpha);
+    method @androidx.compose.runtime.Stable public static long Color(int color);
+    method @androidx.compose.runtime.Stable public static long Color(int red, int green, int blue, optional int alpha);
     method @androidx.compose.runtime.Stable public static long Color(long color);
     method @androidx.compose.runtime.Stable public static long compositeOver(long, long background);
     method public static inline boolean isSpecified(long);
     method public static inline boolean isUnspecified(long);
-    method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, @FloatRange(from=0.0, to=1.0) float fraction);
+    method @androidx.compose.runtime.Stable public static long lerp(long start, long stop, float fraction);
     method @androidx.compose.runtime.Stable public static float luminance(long);
     method public static inline long takeOrElse(long, kotlin.jvm.functions.Function0<androidx.compose.ui.graphics.Color> block);
-    method @ColorInt @androidx.compose.runtime.Stable public static int toArgb(long);
+    method @androidx.compose.runtime.Stable public static int toArgb(long);
   }
 
   @kotlin.jvm.JvmInline public final value class ColorMatrix {
@@ -604,8 +604,8 @@
   public final class OutlineKt {
     method public static void addOutline(androidx.compose.ui.graphics.Path, androidx.compose.ui.graphics.Outline outline);
     method public static void drawOutline(androidx.compose.ui.graphics.Canvas, androidx.compose.ui.graphics.Outline outline, androidx.compose.ui.graphics.Paint paint);
-    method public static void drawOutline(androidx.compose.ui.graphics.drawscope.DrawScope, androidx.compose.ui.graphics.Outline outline, androidx.compose.ui.graphics.Brush brush, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public static void drawOutline(androidx.compose.ui.graphics.drawscope.DrawScope, androidx.compose.ui.graphics.Outline outline, long color, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public static void drawOutline(androidx.compose.ui.graphics.drawscope.DrawScope, androidx.compose.ui.graphics.Outline outline, androidx.compose.ui.graphics.Brush brush, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public static void drawOutline(androidx.compose.ui.graphics.drawscope.DrawScope, androidx.compose.ui.graphics.Outline outline, long color, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
   }
 
   public interface Paint {
@@ -763,7 +763,7 @@
 
   public final class PixelMap {
     ctor public PixelMap(int[] buffer, int width, int height, int bufferOffset, int stride);
-    method public operator long get(@IntRange(from=0L) int x, @IntRange(from=0L) int y);
+    method public operator long get(int x, int y);
     method public int[] getBuffer();
     method public int getBufferOffset();
     method public int getHeight();
@@ -966,8 +966,8 @@
   }
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class ColorModel {
-    method @IntRange(from=1L, to=4L) public int getComponentCount();
-    property @IntRange(from=1L, to=4L) @androidx.compose.runtime.Stable public final int componentCount;
+    method public int getComponentCount();
+    property @androidx.compose.runtime.Stable public final int componentCount;
     field public static final androidx.compose.ui.graphics.colorspace.ColorModel.Companion Companion;
   }
 
@@ -984,18 +984,18 @@
 
   public abstract class ColorSpace {
     ctor public ColorSpace(String name, long model);
-    method @Size(min=3L) public final float[] fromXyz(float x, float y, float z);
-    method @Size(min=3L) public abstract float[] fromXyz(@Size(min=3L) float[] v);
-    method @IntRange(from=1L, to=4L) public final int getComponentCount();
-    method public abstract float getMaxValue(@IntRange(from=0L, to=3L) int component);
-    method public abstract float getMinValue(@IntRange(from=0L, to=3L) int component);
+    method public final float[] fromXyz(float x, float y, float z);
+    method public abstract float[] fromXyz(float[] v);
+    method public final int getComponentCount();
+    method public abstract float getMaxValue(int component);
+    method public abstract float getMinValue(int component);
     method public final long getModel();
     method public final String getName();
     method public boolean isSrgb();
     method public abstract boolean isWideGamut();
-    method @Size(3L) public final float[] toXyz(float r, float g, float b);
-    method @Size(min=3L) public abstract float[] toXyz(@Size(min=3L) float[] v);
-    property @IntRange(from=1L, to=4L) public final int componentCount;
+    method public final float[] toXyz(float r, float g, float b);
+    method public abstract float[] toXyz(float[] v);
+    property public final int componentCount;
     property public boolean isSrgb;
     property public abstract boolean isWideGamut;
     property public final long model;
@@ -1026,7 +1026,7 @@
     method public androidx.compose.ui.graphics.colorspace.Rgb getProPhotoRgb();
     method public androidx.compose.ui.graphics.colorspace.Rgb getSmpteC();
     method public androidx.compose.ui.graphics.colorspace.Rgb getSrgb();
-    method public androidx.compose.ui.graphics.colorspace.ColorSpace? match(@Size(9L) float[] toXYZD50, androidx.compose.ui.graphics.colorspace.TransferParameters function);
+    method public androidx.compose.ui.graphics.colorspace.ColorSpace? match(float[] toXYZD50, androidx.compose.ui.graphics.colorspace.TransferParameters function);
     property public final androidx.compose.ui.graphics.colorspace.Rgb Aces;
     property public final androidx.compose.ui.graphics.colorspace.Rgb Acescg;
     property public final androidx.compose.ui.graphics.colorspace.Rgb AdobeRgb;
@@ -1051,8 +1051,8 @@
     method public final androidx.compose.ui.graphics.colorspace.ColorSpace getDestination();
     method public final int getRenderIntent();
     method public final androidx.compose.ui.graphics.colorspace.ColorSpace getSource();
-    method @Size(3L) public final float[] transform(float r, float g, float b);
-    method @Size(min=3L) public float[] transform(@Size(min=3L) float[] v);
+    method public final float[] transform(float r, float g, float b);
+    method public float[] transform(float[] v);
     property public final androidx.compose.ui.graphics.colorspace.ColorSpace destination;
     property public final int renderIntent;
     property public final androidx.compose.ui.graphics.colorspace.ColorSpace source;
@@ -1096,30 +1096,30 @@
   }
 
   public final class Rgb extends androidx.compose.ui.graphics.colorspace.ColorSpace {
-    ctor public Rgb(@Size(min=1L) String name, @Size(9L) float[] toXYZ, androidx.compose.ui.graphics.colorspace.TransferParameters function);
-    ctor public Rgb(@Size(min=1L) String name, @Size(min=6L, max=9L) float[] primaries, androidx.compose.ui.graphics.colorspace.WhitePoint whitePoint, androidx.compose.ui.graphics.colorspace.TransferParameters function);
-    ctor public Rgb(@Size(min=1L) String name, @Size(min=6L, max=9L) float[] primaries, androidx.compose.ui.graphics.colorspace.WhitePoint whitePoint, double gamma);
-    ctor public Rgb(@Size(min=1L) String name, @Size(min=6L, max=9L) float[] primaries, androidx.compose.ui.graphics.colorspace.WhitePoint whitePoint, kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Double> oetf, kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Double> eotf, float min, float max);
-    ctor public Rgb(@Size(min=1L) String name, @Size(9L) float[] toXYZ, double gamma);
-    ctor public Rgb(@Size(min=1L) String name, @Size(9L) float[] toXYZ, kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Double> oetf, kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Double> eotf);
-    method @Size(3L) public float[] fromLinear(float r, float g, float b);
-    method @Size(min=3L) public float[] fromLinear(@Size(min=3L) float[] v);
+    ctor public Rgb(String name, float[] toXYZ, androidx.compose.ui.graphics.colorspace.TransferParameters function);
+    ctor public Rgb(String name, float[] primaries, androidx.compose.ui.graphics.colorspace.WhitePoint whitePoint, androidx.compose.ui.graphics.colorspace.TransferParameters function);
+    ctor public Rgb(String name, float[] primaries, androidx.compose.ui.graphics.colorspace.WhitePoint whitePoint, double gamma);
+    ctor public Rgb(String name, float[] primaries, androidx.compose.ui.graphics.colorspace.WhitePoint whitePoint, kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Double> oetf, kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Double> eotf, float min, float max);
+    ctor public Rgb(String name, float[] toXYZ, double gamma);
+    ctor public Rgb(String name, float[] toXYZ, kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Double> oetf, kotlin.jvm.functions.Function1<? super java.lang.Double,java.lang.Double> eotf);
+    method public float[] fromLinear(float r, float g, float b);
+    method public float[] fromLinear(float[] v);
     method public float[] fromXyz(float[] v);
     method public kotlin.jvm.functions.Function1<java.lang.Double,java.lang.Double> getEotf();
-    method @Size(9L) public float[] getInverseTransform();
-    method @Size(min=9L) public float[] getInverseTransform(@Size(min=9L) float[] inverseTransform);
+    method public float[] getInverseTransform();
+    method public float[] getInverseTransform(float[] inverseTransform);
     method public float getMaxValue(int component);
     method public float getMinValue(int component);
     method public kotlin.jvm.functions.Function1<java.lang.Double,java.lang.Double> getOetf();
-    method @Size(6L) public float[] getPrimaries();
-    method @Size(min=6L) public float[] getPrimaries(@Size(min=6L) float[] primaries);
+    method public float[] getPrimaries();
+    method public float[] getPrimaries(float[] primaries);
     method public androidx.compose.ui.graphics.colorspace.TransferParameters? getTransferParameters();
-    method @Size(9L) public float[] getTransform();
-    method @Size(min=9L) public float[] getTransform(@Size(min=9L) float[] transform);
+    method public float[] getTransform();
+    method public float[] getTransform(float[] transform);
     method public androidx.compose.ui.graphics.colorspace.WhitePoint getWhitePoint();
     method public boolean isWideGamut();
-    method @Size(3L) public float[] toLinear(float r, float g, float b);
-    method @Size(min=3L) public float[] toLinear(@Size(min=3L) float[] v);
+    method public float[] toLinear(float r, float g, float b);
+    method public float[] toLinear(float[] v);
     method public float[] toXyz(float[] v);
     property public final kotlin.jvm.functions.Function1<java.lang.Double,java.lang.Double> eotf;
     property public boolean isSrgb;
@@ -1174,24 +1174,24 @@
   public final class CanvasDrawScope implements androidx.compose.ui.graphics.drawscope.DrawScope {
     ctor public CanvasDrawScope();
     method public inline void draw(androidx.compose.ui.unit.Density density, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.compose.ui.graphics.Canvas canvas, long size, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.drawscope.DrawScope,kotlin.Unit> block);
-    method public void drawArc(androidx.compose.ui.graphics.Brush brush, float startAngle, float sweepAngle, boolean useCenter, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawArc(long color, float startAngle, float sweepAngle, boolean useCenter, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawCircle(androidx.compose.ui.graphics.Brush brush, float radius, long center, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawCircle(long color, float radius, long center, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawImage(androidx.compose.ui.graphics.ImageBitmap image, long topLeft, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method @Deprecated public void drawImage(androidx.compose.ui.graphics.ImageBitmap image, long srcOffset, long srcSize, long dstOffset, long dstSize, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawLine(androidx.compose.ui.graphics.Brush brush, long start, long end, float strokeWidth, int cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawLine(long color, long start, long end, float strokeWidth, int cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawOval(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawOval(long color, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.Brush brush, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawPath(androidx.compose.ui.graphics.Path path, long color, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, int pointMode, androidx.compose.ui.graphics.Brush brush, float strokeWidth, int cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, int pointMode, long color, float strokeWidth, int cap, androidx.compose.ui.graphics.PathEffect? pathEffect, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawRect(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawRect(long color, long topLeft, long size, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawRoundRect(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, long cornerRadius, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
-    method public void drawRoundRect(long color, long topLeft, long size, long cornerRadius, androidx.compose.ui.graphics.drawscope.DrawStyle style, @FloatRange(from=0.0, to=1.0) float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawArc(androidx.compose.ui.graphics.Brush brush, float startAngle, float sweepAngle, boolean useCenter, long topLeft, long size, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawArc(long color, float startAngle, float sweepAngle, boolean useCenter, long topLeft, long size, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawCircle(androidx.compose.ui.graphics.Brush brush, float radius, long center, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawCircle(long color, float radius, long center, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawImage(androidx.compose.ui.graphics.ImageBitmap image, long topLeft, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method @Deprecated public void drawImage(androidx.compose.ui.graphics.ImageBitmap image, long srcOffset, long srcSize, long dstOffset, long dstSize, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawLine(androidx.compose.ui.graphics.Brush brush, long start, long end, float strokeWidth, int cap, androidx.compose.ui.graphics.PathEffect? pathEffect, float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawLine(long color, long start, long end, float strokeWidth, int cap, androidx.compose.ui.graphics.PathEffect? pathEffect, float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawOval(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawOval(long color, long topLeft, long size, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.Brush brush, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawPath(androidx.compose.ui.graphics.Path path, long color, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, int pointMode, androidx.compose.ui.graphics.Brush brush, float strokeWidth, int cap, androidx.compose.ui.graphics.PathEffect? pathEffect, float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, int pointMode, long color, float strokeWidth, int cap, androidx.compose.ui.graphics.PathEffect? pathEffect, float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawRect(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawRect(long color, long topLeft, long size, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawRoundRect(androidx.compose.ui.graphics.Brush brush, long topLeft, long size, long cornerRadius, float alpha, androidx.compose.ui.graphics.drawscope.DrawStyle style, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
+    method public void drawRoundRect(long color, long topLeft, long size, long cornerRadius, androidx.compose.ui.graphics.drawscope.DrawStyle style, float alpha, androidx.compose.ui.graphics.ColorFilter? colorFilter, int blendMode);
     method public float getDensity();
     method public androidx.compose.ui.graphics.drawscope.DrawContext getDrawContext();
     method public float getFontScale();
@@ -1248,25 +1248,25 @@
   }
 
   @androidx.compose.ui.graphics.drawscope.DrawScopeMarker @kotlin.jvm.JvmDefaultWithCompatibility public interface DrawScope extends androidx.compose.ui.unit.Density {
-    method public void drawArc(androidx.compose.ui.graphics.Brush brush, float startAngle, float sweepAngle, boolean useCenter, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawArc(long color, float startAngle, float sweepAngle, boolean useCenter, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawCircle(androidx.compose.ui.graphics.Brush brush, optional float radius, optional long center, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawCircle(long color, optional float radius, optional long center, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawImage(androidx.compose.ui.graphics.ImageBitmap image, optional long topLeft, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method @Deprecated public void drawImage(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public default void drawImage(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode, optional int filterQuality);
-    method public void drawLine(androidx.compose.ui.graphics.Brush brush, long start, long end, optional float strokeWidth, optional int cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawLine(long color, long start, long end, optional float strokeWidth, optional int cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawOval(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawOval(long color, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.Brush brush, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawPath(androidx.compose.ui.graphics.Path path, long color, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, int pointMode, androidx.compose.ui.graphics.Brush brush, optional float strokeWidth, optional int cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, int pointMode, long color, optional float strokeWidth, optional int cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawRect(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawRect(long color, optional long topLeft, optional long size, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawRoundRect(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional long cornerRadius, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
-    method public void drawRoundRect(long color, optional long topLeft, optional long size, optional long cornerRadius, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional @FloatRange(from=0.0, to=1.0) float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawArc(androidx.compose.ui.graphics.Brush brush, float startAngle, float sweepAngle, boolean useCenter, optional long topLeft, optional long size, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawArc(long color, float startAngle, float sweepAngle, boolean useCenter, optional long topLeft, optional long size, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawCircle(androidx.compose.ui.graphics.Brush brush, optional float radius, optional long center, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawCircle(long color, optional float radius, optional long center, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawImage(androidx.compose.ui.graphics.ImageBitmap image, optional long topLeft, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method @Deprecated public void drawImage(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public default void drawImage(androidx.compose.ui.graphics.ImageBitmap image, optional long srcOffset, optional long srcSize, optional long dstOffset, optional long dstSize, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode, optional int filterQuality);
+    method public void drawLine(androidx.compose.ui.graphics.Brush brush, long start, long end, optional float strokeWidth, optional int cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawLine(long color, long start, long end, optional float strokeWidth, optional int cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawOval(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawOval(long color, optional long topLeft, optional long size, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawPath(androidx.compose.ui.graphics.Path path, androidx.compose.ui.graphics.Brush brush, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawPath(androidx.compose.ui.graphics.Path path, long color, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, int pointMode, androidx.compose.ui.graphics.Brush brush, optional float strokeWidth, optional int cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawPoints(java.util.List<androidx.compose.ui.geometry.Offset> points, int pointMode, long color, optional float strokeWidth, optional int cap, optional androidx.compose.ui.graphics.PathEffect? pathEffect, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawRect(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawRect(long color, optional long topLeft, optional long size, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawRoundRect(androidx.compose.ui.graphics.Brush brush, optional long topLeft, optional long size, optional long cornerRadius, optional float alpha, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
+    method public void drawRoundRect(long color, optional long topLeft, optional long size, optional long cornerRadius, optional androidx.compose.ui.graphics.drawscope.DrawStyle style, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int blendMode);
     method public default long getCenter();
     method public androidx.compose.ui.graphics.drawscope.DrawContext getDrawContext();
     method public androidx.compose.ui.unit.LayoutDirection getLayoutDirection();
diff --git a/compose/ui/ui-graphics/build.gradle b/compose/ui/ui-graphics/build.gradle
index 760efaf..3bb74e1 100644
--- a/compose/ui/ui-graphics/build.gradle
+++ b/compose/ui/ui-graphics/build.gradle
@@ -36,7 +36,6 @@
         commonMain {
             dependencies {
                 implementation(libs.kotlinStdlibCommon)
-                implementation(project(":annotation:annotation"))
 
                 api(project(":compose:ui:ui-unit"))
                 implementation(project(":compose:runtime:runtime"))
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt
index 51b9f61e..0ce5481 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt
@@ -16,10 +16,6 @@
 
 package androidx.compose.ui.graphics
 
-import androidx.annotation.ColorInt
-import androidx.annotation.FloatRange
-import androidx.annotation.IntRange
-import androidx.annotation.Size
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.graphics.colorspace.ColorModel
@@ -466,7 +462,7 @@
  * @return A non-null instance of {@link Color}
  */
 @Stable
-fun Color(@ColorInt color: Int): Color {
+fun Color(/*@ColorInt*/ color: Int): Color {
     return Color(value = color.toULong() shl 32)
 }
 
@@ -501,10 +497,14 @@
  */
 @Stable
 fun Color(
-    @IntRange(from = 0, to = 0xFF) red: Int,
-    @IntRange(from = 0, to = 0xFF) green: Int,
-    @IntRange(from = 0, to = 0xFF) blue: Int,
-    @IntRange(from = 0, to = 0xFF) alpha: Int = 0xFF
+    /*@IntRange(from = 0, to = 0xFF)*/
+    red: Int,
+    /*@IntRange(from = 0, to = 0xFF)*/
+    green: Int,
+    /*@IntRange(from = 0, to = 0xFF)*/
+    blue: Int,
+    /*@IntRange(from = 0, to = 0xFF)*/
+    alpha: Int = 0xFF
 ): Color {
     val color = ((alpha and 0xFF) shl 24) or
         ((red and 0xFF) shl 16) or
@@ -520,7 +520,7 @@
  * in the [ColorSpaces.Oklab] color space.
  */
 @Stable
-fun lerp(start: Color, stop: Color, @FloatRange(from = 0.0, to = 1.0) fraction: Float): Color {
+fun lerp(start: Color, stop: Color, /*@FloatRange(from = 0.0, to = 1.0)*/ fraction: Float): Color {
     val colorSpace = ColorSpaces.Oklab
     val startColor = start.convert(colorSpace)
     val endColor = stop.convert(colorSpace)
@@ -592,7 +592,7 @@
  *
  * @return A new, non-null array whose size is 4
  */
-@Size(value = 4)
+/*@Size(value = 4)*/
 private fun Color.getComponents(): FloatArray = floatArrayOf(red, green, blue, alpha)
 
 /**
@@ -634,7 +634,7 @@
  * @return An ARGB color in the sRGB color space
  */
 @Stable
-@ColorInt
+// @ColorInt
 fun Color.toArgb(): Int {
     return (convert(ColorSpaces.Srgb).value shr 32).toInt()
 }
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Outline.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Outline.kt
index 3aeac0d..40425a0 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Outline.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Outline.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.graphics
 
-import androidx.annotation.FloatRange
 import androidx.compose.runtime.Immutable
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.geometry.Offset
@@ -148,7 +147,8 @@
 fun DrawScope.drawOutline(
     outline: Outline,
     color: Color,
-    @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+    /*@FloatRange(from = 0.0, to = 1.0)*/
+    alpha: Float = 1.0f,
     style: DrawStyle = Fill,
     colorFilter: ColorFilter? = null,
     blendMode: BlendMode = DrawScope.DefaultBlendMode
@@ -187,7 +187,8 @@
 fun DrawScope.drawOutline(
     outline: Outline,
     brush: Brush,
-    @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+    /*@FloatRange(from = 0.0, to = 1.0)*/
+    alpha: Float = 1.0f,
     style: DrawStyle = Fill,
     colorFilter: ColorFilter? = null,
     blendMode: BlendMode = DrawScope.DefaultBlendMode
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PixelMap.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PixelMap.kt
index 9cd8e1a..8212167 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PixelMap.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PixelMap.kt
@@ -16,8 +16,6 @@
 
 package androidx.compose.ui.graphics
 
-import androidx.annotation.IntRange
-
 /**
  * Result of a pixel read operation. This contains the [ImageBitmap] pixel information represented
  * as a 1 dimensional array of values that supports queries of pixel values based on the 2
@@ -48,7 +46,9 @@
      * @param y the vertical pixel coordinate, minimum 1
      */
     operator fun get(
-        @IntRange(from = 0) x: Int,
-        @IntRange(from = 0) y: Int
+        /*@IntRange(from = 0)*/
+        x: Int,
+        /*@IntRange(from = 0)*/
+        y: Int
     ): Color = Color(buffer[bufferOffset + y * stride + x])
 }
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/ColorModel.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/ColorModel.kt
index eb65cf6..8e71d85 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/ColorModel.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/ColorModel.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.graphics.colorspace
 
-import androidx.annotation.IntRange
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.util.packInts
@@ -42,7 +41,7 @@
      *
      * @return An integer between 1 and 4
      */
-    @get:IntRange(from = 1, to = 4)
+    /*@IntRange(from = 1, to = 4)*/
     @Stable
     val componentCount: Int
         get() {
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/ColorSpace.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/ColorSpace.kt
index 3df385d..a754e16 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/ColorSpace.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/ColorSpace.kt
@@ -15,8 +15,6 @@
  */
 package androidx.compose.ui.graphics.colorspace
 
-import androidx.annotation.IntRange
-import androidx.annotation.Size
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.util.packFloats
 import kotlin.math.abs
@@ -152,7 +150,7 @@
      * @see model
      */
     val componentCount: Int
-        @IntRange(from = 1, to = 4)
+        /*@IntRange(from = 1, to = 4)*/
         get() = model.componentCount
 
     /**
@@ -221,7 +219,7 @@
      * @see getMaxValue
      * @see ColorModel.componentCount
      */
-    abstract fun getMinValue(@IntRange(from = 0, to = 3) component: Int): Float
+    abstract fun getMinValue(/*@IntRange(from = 0, to = 3)*/ component: Int): Float
 
     /**
      * Returns the maximum valid value for the specified component of this
@@ -233,7 +231,7 @@
      * @see getMinValue
      * @see ColorModel.componentCount
      */
-    abstract fun getMaxValue(@IntRange(from = 0, to = 3) component: Int): Float
+    abstract fun getMaxValue(/*@IntRange(from = 0, to = 3)*/ component: Int): Float
 
     /**
      * Converts a color value from this color space's model to
@@ -255,7 +253,7 @@
      * @see toXyz
      * @see fromXyz
      */
-    @Size(3)
+    /*@Size(3)*/
     fun toXyz(r: Float, g: Float, b: Float): FloatArray {
         return toXyz(floatArrayOf(r, g, b))
     }
@@ -279,8 +277,8 @@
      * @see toXyz
      * @see fromXyz
      */
-    @Size(min = 3)
-    abstract fun toXyz(@Size(min = 3) v: FloatArray): FloatArray
+    /*@Size(min = 3)*/
+    abstract fun toXyz(/*@Size(min = 3)*/ v: FloatArray): FloatArray
 
     /**
      * Same as [toXyz], but returns only the x and y components packed into a long.
@@ -327,7 +325,7 @@
      * @see fromXyz
      * @see toXyz
      */
-    @Size(min = 3)
+    /*@Size(min = 3)*/
     fun fromXyz(x: Float, y: Float, z: Float): FloatArray {
         val xyz = FloatArray(model.componentCount)
         xyz[0] = x
@@ -355,8 +353,8 @@
      * @see fromXyz
      * @see toXyz
      */
-    @Size(min = 3)
-    abstract fun fromXyz(@Size(min = 3) v: FloatArray): FloatArray
+    /*@Size(min = 3)*/
+    abstract fun fromXyz(/*@Size(min = 3)*/ v: FloatArray): FloatArray
 
     /**
      * Returns a string representation of the object. This method returns
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/ColorSpaces.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/ColorSpaces.kt
index 510faf4..15c35b8 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/ColorSpaces.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/ColorSpaces.kt
@@ -18,8 +18,6 @@
 
 package androidx.compose.ui.graphics.colorspace
 
-import androidx.annotation.Size
-
 object ColorSpaces {
     internal val SrgbPrimaries = floatArrayOf(0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f)
     internal val Ntsc1953Primaries = floatArrayOf(0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f)
@@ -298,7 +296,7 @@
      * @return A non-null [ColorSpace] if a match is found, null otherwise
      */
     fun match(
-        @Size(9)
+        /*@Size(9)*/
         toXYZD50: FloatArray,
         function: TransferParameters
     ): ColorSpace? {
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/Connector.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/Connector.kt
index e12d651..dfe2401 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/Connector.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/Connector.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.graphics.colorspace
 
-import androidx.annotation.Size
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.util.unpackFloat1
 import androidx.compose.ui.util.unpackFloat2
@@ -131,7 +130,7 @@
      *
      * @see transform
      */
-    @Size(3)
+    /*@Size(3)*/
     fun transform(r: Float, g: Float, b: Float): FloatArray {
         return transform(floatArrayOf(r, g, b))
     }
@@ -147,8 +146,8 @@
      *
      * @see transform
      */
-    @Size(min = 3)
-    open fun transform(@Size(min = 3) v: FloatArray): FloatArray {
+    /*@Size(min = 3)*/
+    open fun transform(/*@Size(min = 3)*/ v: FloatArray): FloatArray {
         val xyz = transformSource.toXyz(v)
         if (transform != null) {
             xyz[0] *= transform[0]
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/Rgb.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/Rgb.kt
index d9577e4..fb50d9a 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/Rgb.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/Rgb.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.graphics.colorspace
 
-import androidx.annotation.Size
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.util.packFloats
 import kotlin.math.abs
@@ -323,7 +322,7 @@
      *
      * @see whitePoint
      */
-    @Size(6)
+    /*@Size(6)*/
     fun getPrimaries(): FloatArray = primaries.copyOf()
 
     /**
@@ -340,7 +339,7 @@
      *
      * @see getInverseTransform
      */
-    @Size(9)
+    /*@Size(9)*/
     fun getTransform(): FloatArray = transform.copyOf()
 
     /**
@@ -357,7 +356,7 @@
      *
      * @see getTransform
      */
-    @Size(9)
+    /*@Size(9)*/
     fun getInverseTransform(): FloatArray = inverseTransform.copyOf()
 
     /**
@@ -379,8 +378,10 @@
      *  * The minimum valid value is >= the maximum valid value.
      */
     constructor(
-        @Size(min = 1) name: String,
-        @Size(9) toXYZ: FloatArray,
+        /*@Size(min = 1)*/
+        name: String,
+        /*@Size(9)*/
+        toXYZ: FloatArray,
         oetf: (Double) -> Double,
         eotf: (Double) -> Double
     ) : this(
@@ -430,8 +431,10 @@
      *  * The minimum valid value is >= the maximum valid value.
      */
     constructor(
-        @Size(min = 1) name: String,
-        @Size(min = 6, max = 9) primaries: FloatArray,
+        /*@Size(min = 1)*/
+        name: String,
+        /*@Size(min = 6, max = 9)*/
+        primaries: FloatArray,
         whitePoint: WhitePoint,
         oetf: (Double) -> Double,
         eotf: (Double) -> Double,
@@ -467,8 +470,10 @@
      *  * Gamma is negative.
      */
     constructor(
-        @Size(min = 1) name: String,
-        @Size(9) toXYZ: FloatArray,
+        /*@Size(min = 1)*/
+        name: String,
+        /*@Size(9)*/
+        toXYZ: FloatArray,
         function: TransferParameters
     ) : this(name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), function, MinId)
 
@@ -502,8 +507,10 @@
      *  * The transfer parameters are invalid.
      */
     constructor(
-        @Size(min = 1) name: String,
-        @Size(min = 6, max = 9) primaries: FloatArray,
+        /*@Size(min = 1)*/
+        name: String,
+        /*@Size(min = 6, max = 9)*/
+        primaries: FloatArray,
         whitePoint: WhitePoint,
         function: TransferParameters
     ) : this(name, primaries, whitePoint, function, MinId)
@@ -602,8 +609,10 @@
      * @see get
      */
     constructor(
-        @Size(min = 1) name: String,
-        @Size(9) toXYZ: FloatArray,
+        /*@Size(min = 1)*/
+        name: String,
+        /*@Size(9)*/
+        toXYZ: FloatArray,
         gamma: Double
     ) : this(
         name, computePrimaries(toXYZ), computeWhitePoint(toXYZ), gamma, 0.0f, 1.0f,
@@ -642,8 +651,10 @@
      * @see get
      */
     constructor(
-        @Size(min = 1) name: String,
-        @Size(min = 6, max = 9) primaries: FloatArray,
+        /*@Size(min = 1)*/
+        name: String,
+        /*@Size(min = 6, max = 9)*/
+        primaries: FloatArray,
         whitePoint: WhitePoint,
         gamma: Double
     ) : this(name, primaries, whitePoint, gamma, 0.0f, 1.0f, MinId)
@@ -734,8 +745,8 @@
      *
      * @see getPrimaries
      */
-    @Size(min = 6)
-    fun getPrimaries(@Size(min = 6) primaries: FloatArray): FloatArray {
+    /*@Size(min = 6)*/
+    fun getPrimaries(/*@Size(min = 6)*/ primaries: FloatArray): FloatArray {
         return this.primaries.copyInto(primaries)
     }
 
@@ -756,8 +767,8 @@
      *
      * @see getInverseTransform
      */
-    @Size(min = 9)
-    fun getTransform(@Size(min = 9) transform: FloatArray): FloatArray {
+    /*@Size(min = 9)*/
+    fun getTransform(/*@Size(min = 9)*/ transform: FloatArray): FloatArray {
         return this.transform.copyInto(transform)
     }
 
@@ -779,8 +790,8 @@
      *
      * @see getTransform
      */
-    @Size(min = 9)
-    fun getInverseTransform(@Size(min = 9) inverseTransform: FloatArray): FloatArray {
+    /*@Size(min = 9)*/
+    fun getInverseTransform(/*@Size(min = 9)*/ inverseTransform: FloatArray): FloatArray {
         return this.inverseTransform.copyInto(inverseTransform)
     }
 
@@ -809,7 +820,7 @@
      * @see toLinear
      * @see fromLinear
      */
-    @Size(3)
+    /*@Size(3)*/
     fun toLinear(r: Float, g: Float, b: Float): FloatArray {
         return toLinear(floatArrayOf(r, g, b))
     }
@@ -831,8 +842,8 @@
      * @see toLinear
      * @see fromLinear
      */
-    @Size(min = 3)
-    fun toLinear(@Size(min = 3) v: FloatArray): FloatArray {
+    /*@Size(min = 3)*/
+    fun toLinear(/*@Size(min = 3)*/ v: FloatArray): FloatArray {
         v[0] = eotfFunc(v[0].toDouble()).toFloat()
         v[1] = eotfFunc(v[1].toDouble()).toFloat()
         v[2] = eotfFunc(v[2].toDouble()).toFloat()
@@ -856,7 +867,7 @@
      * @see fromLinear
      * @see toLinear
      */
-    @Size(3)
+    /*@Size(3)*/
     fun fromLinear(r: Float, g: Float, b: Float): FloatArray {
         return fromLinear(floatArrayOf(r, g, b))
     }
@@ -878,8 +889,8 @@
      * @see fromLinear
      * @see toLinear
      */
-    @Size(min = 3)
-    fun fromLinear(@Size(min = 3) v: FloatArray): FloatArray {
+    /*@Size(min = 3)*/
+    fun fromLinear(/*@Size(min = 3) */v: FloatArray): FloatArray {
         v[0] = oetfFunc(v[0].toDouble()).toFloat()
         v[1] = oetfFunc(v[1].toDouble()).toFloat()
         v[2] = oetfFunc(v[2].toDouble()).toFloat()
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/WhitePoint.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/WhitePoint.kt
index b6457ae..ce05ce7 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/WhitePoint.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/WhitePoint.kt
@@ -16,8 +16,6 @@
 
 package androidx.compose.ui.graphics.colorspace
 
-import androidx.annotation.Size
-
 /**
  * Class for constructing white points used in [RGB][Rgb] color space. The value is
  * stored in the CIE xyY color space. The Y component of the white point is assumed
@@ -40,7 +38,7 @@
      *
      * @return A new float array of length 3 containing XYZ values
      */
-    @Size(3)
+    /*@Size(3)*/
     internal fun toXyz(): FloatArray {
         return floatArrayOf(x / y, 1.0f, (1f - x - y) / y)
     }
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
index c8ea35b..a34641d 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/CanvasDrawScope.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.graphics.drawscope
 
-import androidx.annotation.FloatRange
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
@@ -105,7 +104,8 @@
         strokeWidth: Float,
         cap: StrokeCap,
         pathEffect: PathEffect?,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
     ) = drawParams.canvas.drawLine(
@@ -134,7 +134,8 @@
         strokeWidth: Float,
         cap: StrokeCap,
         pathEffect: PathEffect?,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
     ) = drawParams.canvas.drawLine(
@@ -160,7 +161,8 @@
         brush: Brush,
         topLeft: Offset,
         size: Size,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         style: DrawStyle,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -179,7 +181,8 @@
         color: Color,
         topLeft: Offset,
         size: Size,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         style: DrawStyle,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -197,7 +200,8 @@
     override fun drawImage(
         image: ImageBitmap,
         topLeft: Offset,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         style: DrawStyle,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -226,7 +230,8 @@
         srcSize: IntSize,
         dstOffset: IntOffset,
         dstSize: IntSize,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         style: DrawStyle,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -248,7 +253,8 @@
         srcSize: IntSize,
         dstOffset: IntOffset,
         dstSize: IntSize,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         style: DrawStyle,
         colorFilter: ColorFilter?,
         blendMode: BlendMode,
@@ -270,7 +276,8 @@
         topLeft: Offset,
         size: Size,
         cornerRadius: CornerRadius,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         style: DrawStyle,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -293,7 +300,8 @@
         size: Size,
         cornerRadius: CornerRadius,
         style: DrawStyle,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
     ) = drawParams.canvas.drawRoundRect(
@@ -313,7 +321,8 @@
         brush: Brush,
         radius: Float,
         center: Offset,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         style: DrawStyle,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -330,7 +339,8 @@
         color: Color,
         radius: Float,
         center: Offset,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         style: DrawStyle,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -347,7 +357,8 @@
         brush: Brush,
         topLeft: Offset,
         size: Size,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         style: DrawStyle,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -366,7 +377,8 @@
         color: Color,
         topLeft: Offset,
         size: Size,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         style: DrawStyle,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -388,7 +400,8 @@
         useCenter: Boolean,
         topLeft: Offset,
         size: Size,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         style: DrawStyle,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -413,7 +426,8 @@
         useCenter: Boolean,
         topLeft: Offset,
         size: Size,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         style: DrawStyle,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -434,7 +448,8 @@
     override fun drawPath(
         path: Path,
         color: Color,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         style: DrawStyle,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -449,7 +464,8 @@
     override fun drawPath(
         path: Path,
         brush: Brush,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         style: DrawStyle,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
@@ -468,7 +484,8 @@
         strokeWidth: Float,
         cap: StrokeCap,
         pathEffect: PathEffect?,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
     ) = drawParams.canvas.drawPoints(
@@ -497,7 +514,8 @@
         strokeWidth: Float,
         cap: StrokeCap,
         pathEffect: PathEffect?,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         colorFilter: ColorFilter?,
         blendMode: BlendMode
     ) = drawParams.canvas.drawPoints(
@@ -607,7 +625,8 @@
     private fun configurePaint(
         brush: Brush?,
         style: DrawStyle,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         colorFilter: ColorFilter?,
         blendMode: BlendMode,
         filterQuality: FilterQuality = DefaultFilterQuality
@@ -631,7 +650,8 @@
     private fun configurePaint(
         color: Color,
         style: DrawStyle,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         colorFilter: ColorFilter?,
         blendMode: BlendMode,
         filterQuality: FilterQuality = DefaultFilterQuality
@@ -653,7 +673,8 @@
         cap: StrokeCap,
         join: StrokeJoin,
         pathEffect: PathEffect?,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         colorFilter: ColorFilter?,
         blendMode: BlendMode,
         filterQuality: FilterQuality = DefaultFilterQuality
@@ -681,7 +702,8 @@
         cap: StrokeCap,
         join: StrokeJoin,
         pathEffect: PathEffect?,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float,
         colorFilter: ColorFilter?,
         blendMode: BlendMode,
         filterQuality: FilterQuality = DefaultFilterQuality
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt
index 2fa840f..7ef2dc3 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/drawscope/DrawScope.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.graphics.drawscope
 
-import androidx.annotation.FloatRange
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
@@ -378,7 +377,8 @@
         strokeWidth: Float = Stroke.HairlineWidth,
         cap: StrokeCap = Stroke.DefaultCap,
         pathEffect: PathEffect? = null,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
     )
@@ -405,7 +405,8 @@
         strokeWidth: Float = Stroke.HairlineWidth,
         cap: StrokeCap = Stroke.DefaultCap,
         pathEffect: PathEffect? = null,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
     )
@@ -428,7 +429,8 @@
         brush: Brush,
         topLeft: Offset = Offset.Zero,
         size: Size = this.size.offsetSize(topLeft),
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         style: DrawStyle = Fill,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -452,7 +454,8 @@
         color: Color,
         topLeft: Offset = Offset.Zero,
         size: Size = this.size.offsetSize(topLeft),
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         style: DrawStyle = Fill,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -473,7 +476,8 @@
     fun drawImage(
         image: ImageBitmap,
         topLeft: Offset = Offset.Zero,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         style: DrawStyle = Fill,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -517,7 +521,8 @@
         srcSize: IntSize = IntSize(image.width, image.height),
         dstOffset: IntOffset = IntOffset.Zero,
         dstSize: IntSize = srcSize,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         style: DrawStyle = Fill,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -554,7 +559,8 @@
         srcSize: IntSize = IntSize(image.width, image.height),
         dstOffset: IntOffset = IntOffset.Zero,
         dstSize: IntSize = srcSize,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         style: DrawStyle = Fill,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode,
@@ -593,7 +599,8 @@
         topLeft: Offset = Offset.Zero,
         size: Size = this.size.offsetSize(topLeft),
         cornerRadius: CornerRadius = CornerRadius.Zero,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         style: DrawStyle = Fill,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -619,7 +626,8 @@
         size: Size = this.size.offsetSize(topLeft),
         cornerRadius: CornerRadius = CornerRadius.Zero,
         style: DrawStyle = Fill,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
     )
@@ -641,7 +649,8 @@
         brush: Brush,
         radius: Float = size.minDimension / 2.0f,
         center: Offset = this.center,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         style: DrawStyle = Fill,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -664,7 +673,8 @@
         color: Color,
         radius: Float = size.minDimension / 2.0f,
         center: Offset = this.center,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         style: DrawStyle = Fill,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -690,7 +700,8 @@
         brush: Brush,
         topLeft: Offset = Offset.Zero,
         size: Size = this.size.offsetSize(topLeft),
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         style: DrawStyle = Fill,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -716,7 +727,8 @@
         color: Color,
         topLeft: Offset = Offset.Zero,
         size: Size = this.size.offsetSize(topLeft),
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         style: DrawStyle = Fill,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -751,7 +763,8 @@
         useCenter: Boolean,
         topLeft: Offset = Offset.Zero,
         size: Size = this.size.offsetSize(topLeft),
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         style: DrawStyle = Fill,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -786,7 +799,8 @@
         useCenter: Boolean,
         topLeft: Offset = Offset.Zero,
         size: Size = this.size.offsetSize(topLeft),
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         style: DrawStyle = Fill,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -809,7 +823,8 @@
     fun drawPath(
         path: Path,
         color: Color,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         style: DrawStyle = Fill,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -831,7 +846,8 @@
     fun drawPath(
         path: Path,
         brush: Brush,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         style: DrawStyle = Fill,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
@@ -860,7 +876,8 @@
         strokeWidth: Float = Stroke.HairlineWidth,
         cap: StrokeCap = StrokeCap.Butt,
         pathEffect: PathEffect? = null,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
     )
@@ -888,7 +905,8 @@
         strokeWidth: Float = Stroke.HairlineWidth,
         cap: StrokeCap = StrokeCap.Butt,
         pathEffect: PathEffect? = null,
-        @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1.0f,
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        alpha: Float = 1.0f,
         colorFilter: ColorFilter? = null,
         blendMode: BlendMode = DefaultBlendMode
     )
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
index 8ba40fa..a36e3d6 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
@@ -502,23 +502,26 @@
         val box = context.bounds
         val size = box.size.toSize()
         val coordinates = layoutInfo.coordinates
-        val topLeft = toIntOffset(coordinates.localToWindow(Offset.Zero))
-        val topRight = toIntOffset(coordinates.localToWindow(Offset(size.width, 0f)))
-        val bottomRight = toIntOffset(coordinates.localToWindow(Offset(size.width, size.height)))
-        val bottomLeft = toIntOffset(coordinates.localToWindow(Offset(0f, size.height)))
         var bounds: QuadBounds? = null
+        if (layoutInfo.isAttached) {
+            val topLeft = toIntOffset(coordinates.localToWindow(Offset.Zero))
+            val topRight = toIntOffset(coordinates.localToWindow(Offset(size.width, 0f)))
+            val bottomRight =
+                toIntOffset(coordinates.localToWindow(Offset(size.width, size.height)))
+            val bottomLeft = toIntOffset(coordinates.localToWindow(Offset(0f, size.height)))
 
-        if (topLeft.x != box.left || topLeft.y != box.top ||
-            topRight.x != box.right || topRight.y != box.top ||
-            bottomRight.x != box.right || bottomRight.y != box.bottom ||
-            bottomLeft.x != box.left || bottomLeft.y != box.bottom
-        ) {
-            bounds = QuadBounds(
-                topLeft.x, topLeft.y,
-                topRight.x, topRight.y,
-                bottomRight.x, bottomRight.y,
-                bottomLeft.x, bottomLeft.y,
-            )
+            if (topLeft.x != box.left || topLeft.y != box.top ||
+                topRight.x != box.right || topRight.y != box.top ||
+                bottomRight.x != box.right || bottomRight.y != box.bottom ||
+                bottomLeft.x != box.left || bottomLeft.y != box.bottom
+            ) {
+                bounds = QuadBounds(
+                    topLeft.x, topLeft.y,
+                    topRight.x, topRight.y,
+                    bottomRight.x, bottomRight.y,
+                    bottomLeft.x, bottomLeft.y,
+                )
+            }
         }
         if (!includeNodesOutsizeOfWindow) {
             // Ignore this node if the bounds are completely outside the window
diff --git a/compose/ui/ui-text/benchmark/src/androidTest/java/androidx/compose/ui/text/benchmark/HyphensLineBreakBenchmark.kt b/compose/ui/ui-text/benchmark/src/androidTest/java/androidx/compose/ui/text/benchmark/HyphensLineBreakBenchmark.kt
deleted file mode 100644
index 72f0dbc..0000000
--- a/compose/ui/ui-text/benchmark/src/androidTest/java/androidx/compose/ui/text/benchmark/HyphensLineBreakBenchmark.kt
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.text.benchmark
-
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.text.LineBreakConfig
-import android.graphics.text.LineBreaker
-import android.os.Build
-import android.text.Layout
-import android.text.TextPaint
-import androidx.benchmark.junit4.BenchmarkRule
-import androidx.benchmark.junit4.measureRepeated
-import androidx.compose.ui.text.android.InternalPlatformTextApi
-import androidx.compose.ui.text.android.StaticLayoutFactory
-import androidx.compose.ui.text.style.Hyphens
-import androidx.compose.ui.text.style.LineBreak
-import androidx.test.filters.LargeTest
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@LargeTest
-@RunWith(Parameterized::class)
-@OptIn(InternalPlatformTextApi::class)
-class HyphensLineBreakBenchmark(
-    private val textLength: Int,
-    private val hyphensString: String,
-    private val lineBreakString: String
-) {
-    companion object {
-        @JvmStatic
-        @Parameterized.Parameters(name = "length={0} hyphens={1} lineBreak={2}")
-        fun initParameters(): List<Array<Any?>> {
-            return cartesian(
-                arrayOf(32, 128, 512),
-                arrayOf(
-                    Hyphens.None.toTestString(),
-                    Hyphens.Auto.toTestString()
-                ),
-                arrayOf(
-                    LineBreak.Paragraph.toTestString(),
-                    LineBreak.Simple.toTestString(),
-                    LineBreak.Heading.toTestString()
-                )
-            )
-        }
-    }
-
-    @get:Rule
-    val benchmarkRule = BenchmarkRule()
-
-    @get:Rule
-    val textBenchmarkRule = TextBenchmarkTestRule()
-
-    private val width = 100
-    private val textSize: Float = 10F
-    private val hyphenationFrequency = toLayoutHyphenationFrequency(hyphensString.toHyphens())
-    private val lineBreakStyle = toLayoutLineBreakStyle(lineBreakString.toLineBreak().strictness)
-    private val breakStrategy = toLayoutBreakStrategy(lineBreakString.toLineBreak().strategy)
-    private val lineBreakWordStyle =
-        toLayoutLineBreakWordStyle(lineBreakString.toLineBreak().wordBreak)
-
-    @Test
-    fun constructLayout() {
-        textBenchmarkRule.generator { textGenerator ->
-            val text = textGenerator.nextParagraph(textLength)
-            val textPaint = TextPaint()
-            textPaint.textSize = textSize
-            benchmarkRule.measureRepeated {
-                StaticLayoutFactory.create(
-                    text = text,
-                    width = width,
-                    paint = textPaint,
-                    hyphenationFrequency = hyphenationFrequency,
-                    lineBreakStyle = lineBreakStyle,
-                    breakStrategy = breakStrategy,
-                    lineBreakWordStyle = lineBreakWordStyle
-                )
-            }
-        }
-    }
-
-    @Test
-    fun constructLayoutDraw() {
-        textBenchmarkRule.generator { textGenerator ->
-            val text = textGenerator.nextParagraph(textLength)
-            val textPaint = TextPaint()
-            textPaint.textSize = textSize
-            val canvas = Canvas(Bitmap.createBitmap(width, 1000, Bitmap.Config.ARGB_8888))
-            benchmarkRule.measureRepeated {
-                val layout = StaticLayoutFactory.create(
-                    text = text,
-                    width = width,
-                    paint = textPaint,
-                    hyphenationFrequency = hyphenationFrequency,
-                    lineBreakStyle = lineBreakStyle,
-                    breakStrategy = breakStrategy,
-                    lineBreakWordStyle = lineBreakWordStyle
-                )
-                layout.draw(canvas)
-            }
-        }
-    }
-
-    private fun toLayoutHyphenationFrequency(hyphens: Hyphens?): Int = when (hyphens) {
-        Hyphens.Auto -> if (Build.VERSION.SDK_INT <= 32) {
-            Layout.HYPHENATION_FREQUENCY_NORMAL
-        } else {
-            Layout.HYPHENATION_FREQUENCY_NORMAL_FAST
-        }
-        Hyphens.None -> Layout.HYPHENATION_FREQUENCY_NONE
-        else -> Layout.HYPHENATION_FREQUENCY_NONE
-    }
-
-    private fun toLayoutBreakStrategy(breakStrategy: LineBreak.Strategy?): Int =
-        when (breakStrategy) {
-            LineBreak.Strategy.Simple -> LineBreaker.BREAK_STRATEGY_SIMPLE
-            LineBreak.Strategy.HighQuality -> LineBreaker.BREAK_STRATEGY_HIGH_QUALITY
-            LineBreak.Strategy.Balanced -> LineBreaker.BREAK_STRATEGY_BALANCED
-            else -> LineBreaker.BREAK_STRATEGY_SIMPLE
-        }
-
-    private fun toLayoutLineBreakStyle(lineBreakStrictness: LineBreak.Strictness?): Int =
-        when (lineBreakStrictness) {
-            LineBreak.Strictness.Default -> LineBreakConfig.LINE_BREAK_STYLE_NONE
-            LineBreak.Strictness.Loose -> LineBreakConfig.LINE_BREAK_STYLE_LOOSE
-            LineBreak.Strictness.Normal -> LineBreakConfig.LINE_BREAK_STYLE_NORMAL
-            LineBreak.Strictness.Strict -> LineBreakConfig.LINE_BREAK_STYLE_STRICT
-            else -> LineBreakConfig.LINE_BREAK_STYLE_NONE
-        }
-
-    private fun toLayoutLineBreakWordStyle(lineBreakWordStyle: LineBreak.WordBreak?): Int =
-        when (lineBreakWordStyle) {
-            LineBreak.WordBreak.Default -> LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE
-            LineBreak.WordBreak.Phrase -> LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE
-            else -> LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE
-        }
-}
-
-/**
- * Required to make this test work due to a bug with value classes and Parameterized JUnit tests.
- * https://youtrack.jetbrains.com/issue/KT-35523
- *
- * However, it's not enough to use a wrapper because wrapper makes the test name unnecessarily
- * long which causes Perfetto to be unable to create output files with a very long name in some
- * file systems.
- *
- * Using a String instead of an Integer gives us a better test naming.
- */
-private fun String.toLineBreak(): LineBreak = when (this) {
-    "Simple" -> LineBreak.Simple
-    "Heading" -> LineBreak.Heading
-    "Paragraph" -> LineBreak.Paragraph
-    else -> throw IllegalArgumentException("Unrecognized LineBreak value for this test")
-}
-
-private fun LineBreak.toTestString(): String = when (this) {
-    LineBreak.Simple -> "Simple"
-    LineBreak.Heading -> "Heading"
-    LineBreak.Paragraph -> "Paragraph"
-    else -> throw IllegalArgumentException("Unrecognized LineBreak value for this test")
-}
-
-private fun String.toHyphens(): Hyphens = when (this) {
-    "None" -> Hyphens.None
-    "Auto" -> Hyphens.Auto
-    else -> throw IllegalArgumentException("Unrecognized Hyphens value for this test")
-}
-
-private fun Hyphens.toTestString(): String = when (this) {
-    Hyphens.None -> "None"
-    Hyphens.Auto -> "Auto"
-    else -> throw IllegalArgumentException("Unrecognized Hyphens value for this test")
-}
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt
index 1d92b39..c7d3794 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt
@@ -162,12 +162,27 @@
     density: Density
 ): Float {
     return when (lineHeight.type) {
-        TextUnitType.Sp -> with(density) { lineHeight.toPx() }
+        TextUnitType.Sp -> {
+            if (!isNonLinearFontScalingActive(density)) {
+                // Non-linear font scaling is not being used, this SP is safe to use directly.
+                with(density) { lineHeight.toPx() }
+            } else {
+                // Determine the intended line height multiplier and use that, since non-linear font
+                // scaling may compress the line height if it is much larger than the font size.
+                // i.e. preserve the original proportions rather than the absolute converted value.
+                val fontSizeSp = with(density) { contextFontSize.toSp() }
+                val lineHeightMultiplier = lineHeight.value / fontSizeSp.value
+                lineHeightMultiplier * contextFontSize
+            }
+        }
         TextUnitType.Em -> lineHeight.value * contextFontSize
         else -> Float.NaN
     }
 }
 
+// TODO(b/294384826): replace this with the actual platform method once available in core
+private fun isNonLinearFontScalingActive(density: Density) = density.fontScale > 1.05
+
 internal fun Spannable.setSpanStyles(
     contextTextStyle: TextStyle,
     spanStyles: List<AnnotatedString.Range<SpanStyle>>,
diff --git a/compose/ui/ui-tooling-data/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/data/InspectableTests.kt b/compose/ui/ui-tooling-data/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/data/InspectableTests.kt
index a0802ab..196eb55 100644
--- a/compose/ui/ui-tooling-data/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/data/InspectableTests.kt
+++ b/compose/ui/ui-tooling-data/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/data/InspectableTests.kt
@@ -150,9 +150,6 @@
             assertFalse(receiver.parameterCursor.hasNext())
         }
 
-        // Skip Inspectable
-        callCursor.next()
-
         // OneParameter(1)
         validate {
             parameter(name = "a", value = 1, fromDefault = false, static = true, compared = false)
@@ -339,11 +336,11 @@
         val tree = slotTableRecord.store.first().asTree()
         val list = tree.asList()
         val parameters = list.filter { group ->
-            group.parameters.isNotEmpty() && group.location.let {
+            group.parameters.isNotEmpty() && group.name == "Text" && group.location.let {
                 it != null && it.sourceFile == "InspectableTests.kt"
             }
         }
-        val names = parameters.drop(1).first().parameters.map { it.name }
+        val names = parameters.first().parameters.map { it.name }
         assertEquals(
             "text, modifier, color, fontSize, fontStyle, fontWeight, fontFamily, " +
                 "letterSpacing, textDecoration, textAlign, lineHeight, overflow, softWrap, " +
diff --git a/compose/ui/ui-tooling-preview/api/current.txt b/compose/ui/ui-tooling-preview/api/current.txt
index 4d4cc08..e43cd20 100644
--- a/compose/ui/ui-tooling-preview/api/current.txt
+++ b/compose/ui/ui-tooling-preview/api/current.txt
@@ -102,7 +102,7 @@
     property public abstract kotlin.sequences.Sequence<T> values;
   }
 
-  @androidx.compose.ui.tooling.preview.Preview(name="Phone", device=androidx.compose.ui.tooling.preview.Devices.PHONE) @androidx.compose.ui.tooling.preview.Preview(name="Phone - Landscape", device="spec:width = 411dp, height = 891dp, orientation = landscape, dpi = 420") @androidx.compose.ui.tooling.preview.Preview(name="Unfolded Foldable", device=androidx.compose.ui.tooling.preview.Devices.FOLDABLE) @androidx.compose.ui.tooling.preview.Preview(name="Tablet", device=androidx.compose.ui.tooling.preview.Devices.TABLET) @androidx.compose.ui.tooling.preview.Preview(name="Desktop", device=androidx.compose.ui.tooling.preview.Devices.DESKTOP) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.FUNCTION}) public @interface PreviewScreenSizes {
+  @androidx.compose.ui.tooling.preview.Preview(name="Phone", device=androidx.compose.ui.tooling.preview.Devices.PHONE, showSystemUi=true) @androidx.compose.ui.tooling.preview.Preview(name="Phone - Landscape", device="spec:width = 411dp, height = 891dp, orientation = landscape, dpi = 420", showSystemUi=true) @androidx.compose.ui.tooling.preview.Preview(name="Unfolded Foldable", device=androidx.compose.ui.tooling.preview.Devices.FOLDABLE, showSystemUi=true) @androidx.compose.ui.tooling.preview.Preview(name="Tablet", device=androidx.compose.ui.tooling.preview.Devices.TABLET, showSystemUi=true) @androidx.compose.ui.tooling.preview.Preview(name="Desktop", device=androidx.compose.ui.tooling.preview.Devices.DESKTOP, showSystemUi=true) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.FUNCTION}) public @interface PreviewScreenSizes {
   }
 
   public final class Wallpapers {
diff --git a/compose/ui/ui-tooling-preview/api/restricted_current.txt b/compose/ui/ui-tooling-preview/api/restricted_current.txt
index 4d4cc08..e43cd20 100644
--- a/compose/ui/ui-tooling-preview/api/restricted_current.txt
+++ b/compose/ui/ui-tooling-preview/api/restricted_current.txt
@@ -102,7 +102,7 @@
     property public abstract kotlin.sequences.Sequence<T> values;
   }
 
-  @androidx.compose.ui.tooling.preview.Preview(name="Phone", device=androidx.compose.ui.tooling.preview.Devices.PHONE) @androidx.compose.ui.tooling.preview.Preview(name="Phone - Landscape", device="spec:width = 411dp, height = 891dp, orientation = landscape, dpi = 420") @androidx.compose.ui.tooling.preview.Preview(name="Unfolded Foldable", device=androidx.compose.ui.tooling.preview.Devices.FOLDABLE) @androidx.compose.ui.tooling.preview.Preview(name="Tablet", device=androidx.compose.ui.tooling.preview.Devices.TABLET) @androidx.compose.ui.tooling.preview.Preview(name="Desktop", device=androidx.compose.ui.tooling.preview.Devices.DESKTOP) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.FUNCTION}) public @interface PreviewScreenSizes {
+  @androidx.compose.ui.tooling.preview.Preview(name="Phone", device=androidx.compose.ui.tooling.preview.Devices.PHONE, showSystemUi=true) @androidx.compose.ui.tooling.preview.Preview(name="Phone - Landscape", device="spec:width = 411dp, height = 891dp, orientation = landscape, dpi = 420", showSystemUi=true) @androidx.compose.ui.tooling.preview.Preview(name="Unfolded Foldable", device=androidx.compose.ui.tooling.preview.Devices.FOLDABLE, showSystemUi=true) @androidx.compose.ui.tooling.preview.Preview(name="Tablet", device=androidx.compose.ui.tooling.preview.Devices.TABLET, showSystemUi=true) @androidx.compose.ui.tooling.preview.Preview(name="Desktop", device=androidx.compose.ui.tooling.preview.Devices.DESKTOP, showSystemUi=true) @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS, kotlin.annotation.AnnotationTarget.FUNCTION}) public @interface PreviewScreenSizes {
   }
 
   public final class Wallpapers {
diff --git a/compose/ui/ui-tooling-preview/src/androidMain/kotlin/androidx/compose/ui/tooling/preview/MultiPreviews.kt b/compose/ui/ui-tooling-preview/src/androidMain/kotlin/androidx/compose/ui/tooling/preview/MultiPreviews.kt
index 866d472..284dd31 100644
--- a/compose/ui/ui-tooling-preview/src/androidMain/kotlin/androidx/compose/ui/tooling/preview/MultiPreviews.kt
+++ b/compose/ui/ui-tooling-preview/src/androidMain/kotlin/androidx/compose/ui/tooling/preview/MultiPreviews.kt
@@ -35,12 +35,13 @@
         AnnotationTarget.ANNOTATION_CLASS,
         AnnotationTarget.FUNCTION
 )
-@Preview(name = "Phone", device = PHONE)
+@Preview(name = "Phone", device = PHONE, showSystemUi = true)
 @Preview(name = "Phone - Landscape",
-         device = "spec:width = 411dp, height = 891dp, orientation = landscape, dpi = 420")
-@Preview(name = "Unfolded Foldable", device = FOLDABLE)
-@Preview(name = "Tablet", device = TABLET)
-@Preview(name = "Desktop", device = DESKTOP)
+         device = "spec:width = 411dp, height = 891dp, orientation = landscape, dpi = 420",
+         showSystemUi = true)
+@Preview(name = "Unfolded Foldable", device = FOLDABLE, showSystemUi = true)
+@Preview(name = "Tablet", device = TABLET, showSystemUi = true)
+@Preview(name = "Desktop", device = DESKTOP, showSystemUi = true)
 annotation class PreviewScreenSizes
 
 /**
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index e2ac70a..e5c14c2 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -609,7 +609,7 @@
   }
 
   public final class FocusRestorerKt {
-    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier focusRestorer(androidx.compose.ui.Modifier);
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier focusRestorer(androidx.compose.ui.Modifier, optional kotlin.jvm.functions.Function0<androidx.compose.ui.focus.FocusRequester>? onRestoreFailed);
   }
 
   public interface FocusState {
@@ -1872,12 +1872,12 @@
     ctor @Deprecated public PointerInputChange(long id, long uptimeMillis, long position, boolean pressed, long previousUptimeMillis, long previousPosition, boolean previousPressed, androidx.compose.ui.input.pointer.ConsumedData consumed, optional int type);
     ctor public PointerInputChange(long id, long uptimeMillis, long position, boolean pressed, long previousUptimeMillis, long previousPosition, boolean previousPressed, boolean isInitiallyConsumed, optional int type, optional long scrollDelta);
     method public void consume();
-    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional boolean currentPressed, optional float pressure, optional long previousTime, optional long previousPosition, optional boolean previousPressed, optional int type, optional java.util.List<androidx.compose.ui.input.pointer.HistoricalChange> historical, optional long scrollDelta);
     method public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional boolean currentPressed, optional float pressure, optional long previousTime, optional long previousPosition, optional boolean previousPressed, optional int type, optional long scrollDelta);
     method @Deprecated public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional boolean currentPressed, optional long previousTime, optional long previousPosition, optional boolean previousPressed, optional androidx.compose.ui.input.pointer.ConsumedData consumed, optional int type);
     method @Deprecated public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional boolean currentPressed, optional long previousTime, optional long previousPosition, optional boolean previousPressed, androidx.compose.ui.input.pointer.ConsumedData consumed, optional int type, optional long scrollDelta);
     method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional boolean currentPressed, optional long previousTime, optional long previousPosition, optional boolean previousPressed, optional int type, java.util.List<androidx.compose.ui.input.pointer.HistoricalChange> historical, optional long scrollDelta);
     method public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional boolean currentPressed, optional long previousTime, optional long previousPosition, optional boolean previousPressed, optional int type, optional long scrollDelta);
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional long originalEventPosition, optional boolean currentPressed, optional float pressure, optional long previousTime, optional long previousPosition, optional boolean previousPressed, optional int type, optional java.util.List<androidx.compose.ui.input.pointer.HistoricalChange> historical, optional long scrollDelta);
     method @Deprecated public androidx.compose.ui.input.pointer.ConsumedData getConsumed();
     method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public java.util.List<androidx.compose.ui.input.pointer.HistoricalChange> getHistorical();
     method public long getId();
@@ -2653,6 +2653,36 @@
     method public static void invalidateSemantics(androidx.compose.ui.node.SemanticsModifierNode);
   }
 
+  public interface TraversableNode extends androidx.compose.ui.node.DelegatableNode {
+    method public Object getTraverseKey();
+    property public abstract Object traverseKey;
+    field public static final androidx.compose.ui.node.TraversableNode.Companion Companion;
+  }
+
+  public static final class TraversableNode.Companion {
+  }
+
+  public enum TraversableNode.Companion.VisitSubtreeIfAction {
+    method public static androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
+    method public static androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction[] values();
+    enum_constant public static final androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction CancelTraversal;
+    enum_constant public static final androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction SkipSubtree;
+    enum_constant public static final androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction VisitSubtree;
+  }
+
+  public final class TraversableNodeKt {
+    method public static <T extends androidx.compose.ui.node.TraversableNode> T? nearestTraversableAncestor(T);
+    method public static androidx.compose.ui.node.TraversableNode? nearestTraversableAncestorWithKey(androidx.compose.ui.node.DelegatableNode, Object? key);
+    method public static <T extends androidx.compose.ui.node.TraversableNode> void traverseAncestors(T, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> block);
+    method public static void traverseAncestorsWithKey(androidx.compose.ui.node.DelegatableNode, Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.TraversableNode,java.lang.Boolean> block);
+    method public static <T extends androidx.compose.ui.node.TraversableNode> void traverseChildren(T, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> block);
+    method public static void traverseChildrenWithKey(androidx.compose.ui.node.DelegatableNode, Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.TraversableNode,java.lang.Boolean> block);
+    method public static <T extends androidx.compose.ui.node.TraversableNode> void traverseSubtree(T, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> block);
+    method public static <T extends androidx.compose.ui.node.TraversableNode> void traverseSubtreeIf(T, kotlin.jvm.functions.Function1<? super T,? extends androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction> block);
+    method public static void traverseSubtreeIfWithKey(androidx.compose.ui.node.DelegatableNode, Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.TraversableNode,? extends androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction> block);
+    method public static void traverseSubtreeWithKey(androidx.compose.ui.node.DelegatableNode, Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.TraversableNode,java.lang.Boolean> block);
+  }
+
 }
 
 package androidx.compose.ui.platform {
@@ -2681,6 +2711,12 @@
     method public long calculateRecommendedTimeoutMillis(long originalTimeoutMillis, optional boolean containsIcons, optional boolean containsText, optional boolean containsControls);
   }
 
+  public final class AndroidComposeViewAccessibilityDelegateCompat_androidKt {
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static boolean getDisableContentCapture();
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static void setDisableContentCapture(boolean);
+    property @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static final boolean DisableContentCapture;
+  }
+
   public final class AndroidCompositionLocals_androidKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<android.content.res.Configuration> getLocalConfiguration();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<android.content.Context> getLocalContext();
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 9866e4c..71da5eb 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -609,7 +609,7 @@
   }
 
   public final class FocusRestorerKt {
-    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier focusRestorer(androidx.compose.ui.Modifier);
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.ui.Modifier focusRestorer(androidx.compose.ui.Modifier, optional kotlin.jvm.functions.Function0<androidx.compose.ui.focus.FocusRequester>? onRestoreFailed);
   }
 
   public interface FocusState {
@@ -1872,12 +1872,12 @@
     ctor @Deprecated public PointerInputChange(long id, long uptimeMillis, long position, boolean pressed, long previousUptimeMillis, long previousPosition, boolean previousPressed, androidx.compose.ui.input.pointer.ConsumedData consumed, optional int type);
     ctor public PointerInputChange(long id, long uptimeMillis, long position, boolean pressed, long previousUptimeMillis, long previousPosition, boolean previousPressed, boolean isInitiallyConsumed, optional int type, optional long scrollDelta);
     method public void consume();
-    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional boolean currentPressed, optional float pressure, optional long previousTime, optional long previousPosition, optional boolean previousPressed, optional int type, optional java.util.List<androidx.compose.ui.input.pointer.HistoricalChange> historical, optional long scrollDelta);
     method public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional boolean currentPressed, optional float pressure, optional long previousTime, optional long previousPosition, optional boolean previousPressed, optional int type, optional long scrollDelta);
     method @Deprecated public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional boolean currentPressed, optional long previousTime, optional long previousPosition, optional boolean previousPressed, optional androidx.compose.ui.input.pointer.ConsumedData consumed, optional int type);
     method @Deprecated public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional boolean currentPressed, optional long previousTime, optional long previousPosition, optional boolean previousPressed, androidx.compose.ui.input.pointer.ConsumedData consumed, optional int type, optional long scrollDelta);
     method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional boolean currentPressed, optional long previousTime, optional long previousPosition, optional boolean previousPressed, optional int type, java.util.List<androidx.compose.ui.input.pointer.HistoricalChange> historical, optional long scrollDelta);
     method public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional boolean currentPressed, optional long previousTime, optional long previousPosition, optional boolean previousPressed, optional int type, optional long scrollDelta);
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public androidx.compose.ui.input.pointer.PointerInputChange copy(optional long id, optional long currentTime, optional long currentPosition, optional long originalEventPosition, optional boolean currentPressed, optional float pressure, optional long previousTime, optional long previousPosition, optional boolean previousPressed, optional int type, optional java.util.List<androidx.compose.ui.input.pointer.HistoricalChange> historical, optional long scrollDelta);
     method @Deprecated public androidx.compose.ui.input.pointer.ConsumedData getConsumed();
     method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public java.util.List<androidx.compose.ui.input.pointer.HistoricalChange> getHistorical();
     method public long getId();
@@ -2706,6 +2706,36 @@
     method public static void invalidateSemantics(androidx.compose.ui.node.SemanticsModifierNode);
   }
 
+  public interface TraversableNode extends androidx.compose.ui.node.DelegatableNode {
+    method public Object getTraverseKey();
+    property public abstract Object traverseKey;
+    field public static final androidx.compose.ui.node.TraversableNode.Companion Companion;
+  }
+
+  public static final class TraversableNode.Companion {
+  }
+
+  public enum TraversableNode.Companion.VisitSubtreeIfAction {
+    method public static androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
+    method public static androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction[] values();
+    enum_constant public static final androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction CancelTraversal;
+    enum_constant public static final androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction SkipSubtree;
+    enum_constant public static final androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction VisitSubtree;
+  }
+
+  public final class TraversableNodeKt {
+    method public static <T extends androidx.compose.ui.node.TraversableNode> T? nearestTraversableAncestor(T);
+    method public static androidx.compose.ui.node.TraversableNode? nearestTraversableAncestorWithKey(androidx.compose.ui.node.DelegatableNode, Object? key);
+    method public static <T extends androidx.compose.ui.node.TraversableNode> void traverseAncestors(T, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> block);
+    method public static void traverseAncestorsWithKey(androidx.compose.ui.node.DelegatableNode, Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.TraversableNode,java.lang.Boolean> block);
+    method public static <T extends androidx.compose.ui.node.TraversableNode> void traverseChildren(T, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> block);
+    method public static void traverseChildrenWithKey(androidx.compose.ui.node.DelegatableNode, Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.TraversableNode,java.lang.Boolean> block);
+    method public static <T extends androidx.compose.ui.node.TraversableNode> void traverseSubtree(T, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> block);
+    method public static <T extends androidx.compose.ui.node.TraversableNode> void traverseSubtreeIf(T, kotlin.jvm.functions.Function1<? super T,? extends androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction> block);
+    method public static void traverseSubtreeIfWithKey(androidx.compose.ui.node.DelegatableNode, Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.TraversableNode,? extends androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction> block);
+    method public static void traverseSubtreeWithKey(androidx.compose.ui.node.DelegatableNode, Object? key, kotlin.jvm.functions.Function1<? super androidx.compose.ui.node.TraversableNode,java.lang.Boolean> block);
+  }
+
 }
 
 package androidx.compose.ui.platform {
@@ -2734,6 +2764,12 @@
     method public long calculateRecommendedTimeoutMillis(long originalTimeoutMillis, optional boolean containsIcons, optional boolean containsText, optional boolean containsControls);
   }
 
+  public final class AndroidComposeViewAccessibilityDelegateCompat_androidKt {
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static boolean getDisableContentCapture();
+    method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static void setDisableContentCapture(boolean);
+    property @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static final boolean DisableContentCapture;
+  }
+
   public final class AndroidCompositionLocals_androidKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<android.content.res.Configuration> getLocalConfiguration();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<android.content.Context> getLocalContext();
diff --git a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/graphics/vector/CreateVectorPainterBenchmark.kt b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/graphics/vector/CreateVectorPainterBenchmark.kt
new file mode 100644
index 0000000..e96dac5
--- /dev/null
+++ b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/graphics/vector/CreateVectorPainterBenchmark.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.benchmark.graphics.vector
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.ComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkDraw
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.benchmark.R
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class CreateVectorPainterBenchmark {
+
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    @Test
+    fun recreateContent() {
+        benchmarkRule.toggleStateBenchmarkDraw({
+            RecreateVectorPainterTestCase()
+        }, assertOneRecomposition = false)
+    }
+}
+
+private class RecreateVectorPainterTestCase : ComposeTestCase, ToggleableTestCase {
+
+    private var alpha by mutableStateOf(1f)
+
+    @Composable
+    override fun Content() {
+        Column {
+            Box(modifier = Modifier.wrapContentSize()) {
+                Image(
+                    painter = painterResource(R.drawable.ic_hourglass),
+                    contentDescription = null,
+                    modifier = Modifier.size(200.dp),
+                    alpha = alpha
+                )
+            }
+        }
+    }
+
+    override fun toggleState() {
+        if (alpha == 1.0f) {
+            alpha = 0.5f
+        } else {
+            alpha = 1.0f
+        }
+    }
+}
diff --git a/compose/ui/ui/benchmark/src/main/res/drawable/ic_hourglass.xml b/compose/ui/ui/benchmark/src/main/res/drawable/ic_hourglass.xml
new file mode 100644
index 0000000..1666c76
--- /dev/null
+++ b/compose/ui/ui/benchmark/src/main/res/drawable/ic_hourglass.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2019 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24" >
+    <group
+        android:name="hourglass_frame"
+        android:translateX="12"
+        android:translateY="12"
+        android:scaleX="0.75"
+        android:scaleY="0.75" >
+        <group
+            android:name="hourglass_frame_pivot"
+            android:translateX="-12"
+            android:translateY="-12" >
+            <group
+                android:name="group_2_2"
+                android:translateX="12"
+                android:translateY="6.5" >
+                <path
+                    android:name="path_2_2"
+                    android:pathData="M 6.52099609375 -3.89300537109 c 0.0 0.0 -6.52099609375 6.87901306152 -6.52099609375 6.87901306152 c 0 0.0 -6.52099609375 -6.87901306152 -6.52099609375 -6.87901306152 c 0.0 0.0 13.0419921875 0.0 13.0419921875 0.0 Z M 9.99800109863 -6.5 c 0.0 0.0 -19.9960021973 0.0 -19.9960021973 0.0 c -0.890991210938 0.0 -1.33700561523 1.07699584961 -0.707000732422 1.70700073242 c 0.0 0.0 10.7050018311 11.2929992676 10.7050018311 11.2929992676 c 0 0.0 10.7050018311 -11.2929992676 10.7050018311 -11.2929992676 c 0.630004882812 -0.630004882812 0.183990478516 -1.70700073242 -0.707000732422 -1.70700073242 Z"
+                    android:fillColor="#FF777777" />
+            </group>
+            <group
+                android:name="group_1_2"
+                android:translateX="12"
+                android:translateY="17.5" >
+                <path
+                    android:name="path_2_1"
+                    android:pathData="M 0 -2.98600769043 c 0 0.0 6.52099609375 6.87901306152 6.52099609375 6.87901306152 c 0.0 0.0 -13.0419921875 0.0 -13.0419921875 0.0 c 0.0 0.0 6.52099609375 -6.87901306152 6.52099609375 -6.87901306152 Z M 0 -6.5 c 0 0.0 -10.7050018311 11.2929992676 -10.7050018311 11.2929992676 c -0.630004882812 0.630004882812 -0.184005737305 1.70700073242 0.707000732422 1.70700073242 c 0.0 0.0 19.9960021973 0.0 19.9960021973 0.0 c 0.890991210938 0.0 1.33699035645 -1.07699584961 0.707000732422 -1.70700073242 c 0.0 0.0 -10.7050018311 -11.2929992676 -10.7050018311 -11.2929992676 Z"
+                    android:fillColor="#FF777777" />
+            </group>
+        </group>
+    </group>
+    <group
+        android:name="fill_outlines"
+        android:translateX="12"
+        android:translateY="12"
+        android:scaleX="0.75"
+        android:scaleY="0.75" >
+        <group
+            android:name="fill_outlines_pivot"
+            android:translateX="-12"
+            android:translateY="-12" >
+            <clip-path
+                android:name="mask_1"
+                android:pathData="M 24 13.3999938965 c 0 0.0 -24 0.0 -24 0.0 c 0 0.0 0 10.6000061035 0 10.6000061035 c 0 0 24 0 24 0 c 0 0 0 -10.6000061035 0 -10.6000061035 Z" />
+            <group
+                android:name="group_1_3"
+                android:translateX="12"
+                android:translateY="12" >
+                <path
+                    android:name="path_1_6"
+                    android:pathData="M 10.7100067139 10.2900085449 c 0.629989624023 0.629989624023 0.179992675781 1.70999145508 -0.710006713867 1.70999145508 c 0 0 -20 0 -20 0 c -0.889999389648 0 -1.33999633789 -1.08000183105 -0.710006713867 -1.70999145508 c 0.0 0.0 9.76000976562 -10.2900085449 9.76000976563 -10.2900085449 c 0.0 0 -9.76000976562 -10.2899932861 -9.76000976563 -10.2899932861 c -0.629989624023 -0.630004882812 -0.179992675781 -1.71000671387 0.710006713867 -1.71000671387 c 0 0 20 0 20 0 c 0.889999389648 0 1.33999633789 1.08000183105 0.710006713867 1.71000671387 c 0.0 0.0 -9.76000976562 10.2899932861 -9.76000976563 10.2899932861 c 0.0 0 9.76000976562 10.2900085449 9.76000976563 10.2900085449 Z"
+                    android:fillColor="#FF777777" />
+            </group>
+        </group>
+    </group>
+</vector>
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/ViewInterop.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/ViewInterop.kt
index 2ea6de9..22effa7 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/ViewInterop.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/ViewInterop.kt
@@ -16,16 +16,27 @@
 
 package androidx.compose.ui.demos.viewinterop
 
+import android.annotation.SuppressLint
 import android.content.Context
 import android.graphics.Canvas
 import android.graphics.Paint
 import android.graphics.Rect
+import android.os.Build
 import android.view.View
 import android.view.ViewGroup
+import android.widget.LinearLayout
 import android.widget.TextView
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.Button
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
@@ -38,9 +49,11 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.node.Ref
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.compose.ui.viewinterop.AndroidViewBinding
 
+@SuppressLint("ResourceType")
 @Composable
 fun ViewInteropDemo() {
     Column {
@@ -85,6 +98,62 @@
         ) {
             Text("Change color of Android view")
         }
+
+        Column(modifier =
+            Modifier.fillMaxWidth().height(100.dp).verticalScroll(rememberScrollState())
+        ) {
+            AndroidView({ c ->
+                LinearLayout(c).apply {
+                    val text1 = TextView(c).apply { text = "LinearLayout child 1"; id = 11 }
+                    val text2 = TextView(c).apply { text = "LinearLayout child 2"; id = 22 }
+                    val text3 = TextView(c).apply { text = "LinearLayout child 3"; id = 33 }
+                    if (Build.VERSION.SDK_INT >= 26) {
+                        Api26Impl.setAccessibilityTraversalAfter(text3, text2.getId());
+                        Api26Impl.setAccessibilityTraversalAfter(text2, text1.getId());
+                    }
+                    addView(text3)
+                    addView(text2)
+                    addView(text1)
+                }
+            })
+        }
+
+        Spacer(Modifier.height(20.dp))
+
+        LazyColumn(
+            modifier = Modifier.fillMaxWidth().height(50.dp)
+        ) {
+            item {
+                AndroidView(::TextView) { it.text = "TextView in LazyColumn 1A" }
+                AndroidView(::TextView) { it.text = "TextView in LazyColumn 1B" }
+            }
+            item {
+                AndroidView(::TextView) { it.text = "TextView in LazyColumn 2A" }
+                AndroidView(::TextView) { it.text = "TextView in LazyColumn 2B" }
+            }
+            item {
+                AndroidView(::TextView) { it.text = "TextView in LazyColumn 3A" }
+                AndroidView(::TextView) { it.text = "TextView in LazyColumn 3B" }
+            }
+            item {
+                AndroidView(::TextView) { it.text = "TextView in LazyColumn 4A" }
+                AndroidView(::TextView) { it.text = "TextView in LazyColumn 4B" }
+            }
+        }
+        Spacer(Modifier.height(20.dp))
+        Column(
+            modifier = Modifier.fillMaxWidth().height(50.dp).verticalScroll(rememberScrollState())
+        ) {
+            AndroidView(::TextView) { it.text = "TextView in verticalScroll 1" }
+            AndroidView(::TextView) { it.text = "TextView in verticalScroll 2" }
+            AndroidView(::TextView) { it.text = "TextView in verticalScroll 3" }
+            AndroidView(::TextView) { it.text = "TextView in verticalScroll 4" }
+            AndroidView(::TextView) { it.text = "TextView in verticalScroll 5" }
+            AndroidView(::TextView) { it.text = "TextView in verticalScroll 6" }
+            AndroidView(::TextView) { it.text = "TextView in verticalScroll 7" }
+            AndroidView(::TextView) { it.text = "TextView in verticalScroll 8" }
+            AndroidView(::TextView) { it.text = "TextView in verticalScroll 9" }
+        }
     }
 }
 
@@ -120,3 +189,15 @@
         canvas.drawRect(rect, paint)
     }
 }
+
+@RequiresApi(Build.VERSION_CODES.O)
+private object Api26Impl {
+    @DoNotInline
+    @JvmStatic
+    fun setAccessibilityTraversalAfter(
+        view: View,
+        id: Int
+    ) {
+        view.setAccessibilityTraversalAfter(id);
+    }
+}
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
index 69035e5..4041e63 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/FocusSamples.kt
@@ -134,6 +134,27 @@
     }
 }
 
+@OptIn(ExperimentalComposeUiApi::class)
+@Sampled
+@Composable
+fun FocusRestorerCustomFallbackSample() {
+    val focusRequester = remember { FocusRequester() }
+    LazyRow(
+        // If restoration fails, focus would fallback to the item associated with focusRequester.
+        Modifier.focusRestorer { focusRequester }
+    ) {
+        item {
+            Button(
+                modifier = Modifier.focusRequester(focusRequester),
+                onClick = {}
+            ) { Text("1") }
+        }
+        item { Button(onClick = {}) { Text("2") } }
+        item { Button(onClick = {}) { Text("3") } }
+        item { Button(onClick = {}) { Text("4") } }
+    }
+}
+
 @Sampled
 @Composable
 fun RequestFocusSample() {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawModifierTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawModifierTest.kt
index 0a41454..299ab79 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawModifierTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawModifierTest.kt
@@ -48,6 +48,7 @@
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.node.DelegatingNode
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.platform.LocalLayoutDirection
@@ -820,6 +821,34 @@
             .assertHeightIsEqualTo(10.dp)
     }
 
+    @Test
+    fun testInvalidationInsideOnSizeChanged() {
+        var someState by mutableStateOf(1)
+        var drawCount = 0
+
+        rule.setContent {
+            Box(
+                Modifier
+                    .drawBehind {
+                        @Suppress("UNUSED_EXPRESSION")
+                        someState
+                        drawCount++
+                    }
+                    .onSizeChanged {
+                        // assert that draw hasn't happened yet
+                        assertEquals(0, drawCount)
+                        someState++
+                    }
+                    .size(10.dp)
+            )
+        }
+        rule.runOnIdle {
+            // assert that state invalidation inside of onSizeChanged
+            // doesn't schedule additional draw
+            assertEquals(1, drawCount)
+        }
+    }
+
     // captureToImage() requires API level 26
     @RequiresApi(Build.VERSION_CODES.O)
     private fun SemanticsNodeInteraction.captureToBitmap() = captureToImage().asAndroidBitmap()
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusRestorerTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusRestorerTest.kt
index dbe8b98..469567e 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusRestorerTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusRestorerTest.kt
@@ -177,4 +177,48 @@
             assertThat(grandChildState.isFocused).isFalse()
         }
     }
+
+    @Test
+    fun restorationFailed_fallbackToOnRestoreFailedDestination() {
+        // Arrange.
+        val (parent, child2) = FocusRequester.createRefs()
+        lateinit var child1State: FocusState
+        lateinit var child2State: FocusState
+        rule.setFocusableContent {
+            Box(
+                Modifier
+                    .size(10.dp)
+                    .focusRequester(parent)
+                    .focusRestorer { child2 }
+                    .focusGroup()
+            ) {
+                key(1) {
+                    Box(
+                        Modifier
+                            .size(10.dp)
+                            .onFocusChanged { child1State = it }
+                            .focusTarget()
+                    )
+                }
+                key(2) {
+                    Box(
+                        Modifier
+                            .size(10.dp)
+                            .focusRequester(child2)
+                            .onFocusChanged { child2State = it }
+                            .focusTarget()
+                    )
+                }
+            }
+        }
+
+        // Act.
+        rule.runOnIdle { parent.requestFocus() }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(child1State.isFocused).isFalse()
+            assertThat(child2State.isFocused).isTrue()
+        }
+    }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/KeyEventToFocusDirectionTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/KeyEventToFocusDirectionTest.kt
index 94dce03..fb9d369 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/KeyEventToFocusDirectionTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/KeyEventToFocusDirectionTest.kt
@@ -107,6 +107,30 @@
     }
 
     @Test
+    fun page_up() {
+        // Arrange.
+        val keyEvent = KeyEvent(AndroidKeyEvent(KeyDown, Key.PageUp.nativeKeyCode))
+
+        // Act.
+        val focusDirection = owner.getFocusDirection(keyEvent)
+
+        // Assert.
+        assertThat(focusDirection).isEqualTo(Up)
+    }
+
+    @Test
+    fun page_down() {
+        // Arrange.
+        val keyEvent = KeyEvent(AndroidKeyEvent(KeyDown, Key.PageDown.nativeKeyCode))
+
+        // Act.
+        val focusDirection = owner.getFocusDirection(keyEvent)
+
+        // Assert.
+        assertThat(focusDirection).isEqualTo(Down)
+    }
+
+    @Test
     fun tab_next() {
         // Arrange.
         val keyEvent = KeyEvent(AndroidKeyEvent(KeyDown, Key.Tab.nativeKeyCode))
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapterTest.kt
index c06be12..77cdb77 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapterTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapterTest.kt
@@ -1289,7 +1289,15 @@
         val pointers = pointerInputEvent.pointers
         assertThat(uptime).isEqualTo(0L)
         assertThat(pointers).hasSize(1)
-        assertPointerInputEventData(pointers[0], PointerId(0), true, 1f, 2f)
+        assertPointerInputEventData(
+            pointers[0],
+            PointerId(0),
+            true,
+            1f,
+            2f,
+            originalX = 11f,
+            originalY = 22f
+        )
     }
 
     @Test
@@ -1659,6 +1667,46 @@
     }
 
     @Test
+    fun convertToPointerInputEvent_differentCoordinateSpace_useOriginalPointCoordinate() {
+        motionEventAdapter.convertToPointerInputEvent(
+            MotionEvent(
+                10,
+                ACTION_DOWN,
+                1,
+                0,
+                arrayOf(PointerProperties(46)),
+                arrayOf(PointerCoords(3f, 4f))
+            )
+        )
+        val motionEvent = MotionEvent(
+            34,
+            ACTION_MOVE,
+            1,
+            0,
+            arrayOf(PointerProperties(46)),
+            arrayOf(PointerCoords(30f, 40f))
+        )
+
+        val positionCalculator = object : PositionCalculator by positionCalculator {
+            override fun screenToLocal(positionOnScreen: Offset): Offset {
+                return positionOnScreen / 2f
+            }
+        }
+
+        val pointerInputEvent =
+            motionEventAdapter.convertToPointerInputEvent(motionEvent, positionCalculator)
+        assertPointerInputEventData(
+            pointerInputEvent!!.pointers[0],
+            PointerId(0),
+            true,
+            30f,
+            40f,
+            originalX = 30f,
+            originalY = 40f
+        )
+    }
+
+    @Test
     fun convertScrollEvent_horizontalPositive() {
         val motionEvent = MotionEvent(
             eventTime = 1,
@@ -1791,11 +1839,15 @@
     isDown: Boolean,
     x: Float,
     y: Float,
-    type: PointerType = PointerType.Touch
+    type: PointerType = PointerType.Touch,
+    originalX: Float = x,
+    originalY: Float = y,
 ) {
     assertThat(actual.id).isEqualTo(id)
     assertThat(actual.down).isEqualTo(isDown)
     assertThat(actual.positionOnScreen.x).isEqualTo(x)
     assertThat(actual.positionOnScreen.y).isEqualTo(y)
+    assertThat(actual.originalEventPosition.x).isEqualTo(originalX)
+    assertThat(actual.originalEventPosition.y).isEqualTo(originalY)
     assertThat(actual.type).isEqualTo(type)
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerIconTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerIconTest.kt
index 26e13a8..cacc19c 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerIconTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerIconTest.kt
@@ -20,14 +20,20 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
 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.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.platform.InspectableValue
@@ -57,10 +63,10 @@
     private val parentIconTag = "myParentIcon"
     private val childIconTag = "myChildIcon"
     private val grandchildIconTag = "myGrandchildIcon"
-    private val desiredParentIcon = PointerIcon.Crosshair
-    private val desiredChildIcon = PointerIcon.Text
-    private val desiredGrandchildIcon = PointerIcon.Hand
-    private val desiredDefaultIcon = PointerIcon.Default
+    private val desiredParentIcon = PointerIcon.Crosshair // AndroidPointerIcon(type=1007)
+    private val desiredChildIcon = PointerIcon.Text // AndroidPointerIcon(type=1008)
+    private val desiredGrandchildIcon = PointerIcon.Hand // AndroidPointerIcon(type=1002)
+    private val desiredDefaultIcon = PointerIcon.Default // AndroidPointerIcon(type=1000)
     private lateinit var iconService: PointerIconService
 
     @Before
@@ -458,7 +464,6 @@
      *  Parent Box (output icon = [PointerIcon.Crosshair])
      *    ⤷ Child Box (output icon = [PointerIcon.Crosshair])
      */
-    @Ignore("b/267170292 - not yet implemented")
     @Test
     fun parentChildPartialOverlap_parentModifierDynamicallyAdded() {
         val isVisible = mutableStateOf(false)
@@ -526,7 +531,7 @@
      *  Parent Box (output icon = [PointerIcon.Crosshair])
      *    ⤷ Child Box (output icon = [PointerIcon.Crosshair])
      */
-    @Ignore("b/267170292 - not yet implemented")
+    @Ignore("b/299482894 - not yet implemented")
     @Test
     fun parentChildPartialOverlap_parentModifierDynamicallyAddedWithMoveEvents() {
         val isVisible = mutableStateOf(false)
@@ -605,6 +610,159 @@
 
     /**
      * Setup:
+     * The hierarchy for the initial setup of this test is:
+     *  Parent Box (custom icon = [PointerIcon.Crosshair], overrideDescendants = FALSE)
+     *      ⤷ Child Box (custom icon = [PointerIcon.Text], overrideDescendants = FALSE)
+     *
+     *  After hovering over the center of the screen, the hierarchy under the cursor updates to:
+     *  Parent Box (custom icon = [PointerIcon.Crosshair], overrideDescendants = TRUE)
+     *    ⤷ Child Box (custom icon = [PointerIcon.Text], overrideDescendants = FALSE)
+     *
+     *  After several assertions, it reverts back to false in the parent:
+     *  Parent Box (custom icon = [PointerIcon.Crosshair], overrideDescendants = FALSE)
+     *    ⤷ Child Box (custom icon = [PointerIcon.Text], overrideDescendants = FALSE)
+     *
+     *
+     *  Expected Output:
+     *  Initially, the Child Box's [PointerIcon.Text] should win for its entire surface area
+     *  because the parent does not override descendants. After the Parent Box dynamically changes
+     *  overrideDescendants to true, the Parent Box's [PointerIcon.Crosshair] should win for the
+     *  entire surface area of the Parent Box and Child Box because the Parent Box has
+     *  overrideDescendants = true.
+     *
+     *  It should then revert back to Child Box's [PointerIcon.Text] after the Parent Box's
+     *  overrideDescendants is set back to false.
+     *
+     */
+    @Test
+    fun parentChildPartialOverlap_parentModifierDynamicallyChangedToOverrideWithMoveEvents() {
+        var parentOverrideDescendants by mutableStateOf(false)
+        rule.setContent {
+            CompositionLocalProvider(LocalPointerIconService provides iconService) {
+                Box(
+                    modifier = Modifier
+                        .requiredSize(200.dp)
+                        .border(BorderStroke(2.dp, SolidColor(Color.Red)))
+                        .testTag(parentIconTag)
+                        .then(
+                            Modifier.pointerHoverIcon(
+                                desiredParentIcon,
+                                overrideDescendants = parentOverrideDescendants
+                            )
+                        )
+
+                ) {
+                    Box(
+                        Modifier
+                            .padding(20.dp)
+                            .requiredSize(150.dp)
+                            .border(BorderStroke(2.dp, SolidColor(Color.Black)))
+                            .testTag(childIconTag)
+                            .pointerHoverIcon(desiredChildIcon, overrideDescendants = false)
+                    )
+                }
+            }
+        }
+        // Verify initial state of pointer icon
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredDefaultIcon)
+        }
+        // Hover over Child Box and verify it has the desired child icon
+        rule.onNodeWithTag(childIconTag).performMouseInput {
+            enter(bottomRight)
+        }
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredChildIcon)
+        }
+        // Move to Parent Box and verify its icon is the desired parent icon
+        rule.onNodeWithTag(parentIconTag).performMouseInput {
+            moveTo(bottomRight)
+        }
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredParentIcon)
+        }
+        // Move back to the Child Box
+        rule.onNodeWithTag(childIconTag).performMouseInput {
+            moveTo(center)
+        }
+
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredChildIcon)
+        }
+
+        // Dynamically change the pointerHoverIcon Modifier to the Parent Box to
+        // override descendants.
+        rule.runOnIdle {
+            parentOverrideDescendants = true
+        }
+
+        // Verify the Child Box has updated to respect the desired parent icon
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredParentIcon)
+        }
+
+        // Move within the Child Box and verify it is still respecting the desired parent icon
+        rule.onNodeWithTag(childIconTag).performMouseInput {
+            moveTo(bottomRight)
+        }
+
+        // Verify the Child Box has updated to respect the desired parent icon
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredParentIcon)
+        }
+
+        // Move to the Parent Box and verify it also has the desired parent icon
+        rule.onNodeWithTag(parentIconTag).performMouseInput {
+            moveTo(bottomRight)
+        }
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredParentIcon)
+        }
+
+        // Move within the Child Box and verify it is still respecting the desired parent icon
+        rule.onNodeWithTag(childIconTag).performMouseInput {
+            moveTo(bottomRight)
+        }
+
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredParentIcon)
+        }
+
+        // Dynamically change the pointerHoverIcon Modifier to the Parent Box to NOT
+        // override descendants.
+        rule.runOnIdle {
+            parentOverrideDescendants = false
+        }
+
+        // Verify it's changed to child icon
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredChildIcon)
+        }
+
+        // Move to Parent Box and verify its icon is the desired parent icon
+        rule.onNodeWithTag(parentIconTag).performMouseInput {
+            moveTo(bottomRight)
+        }
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredParentIcon)
+        }
+        // Move back to the Child Box
+        rule.onNodeWithTag(childIconTag).performMouseInput {
+            moveTo(center)
+        }
+
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredChildIcon)
+        }
+
+        // Exit hovering over Parent Box
+        rule.onNodeWithTag(parentIconTag).performMouseInput {
+            exit()
+        }
+    }
+
+    /**
+     * Setup:
      *  The hierarchy for the initial setup of this test is:
      *  Parent Box (custom icon = [PointerIcon.Crosshair], overrideDescendants = FALSE)
      *      ⤷ Child Box (custom icon = [PointerIcon.Text], overrideDescendants = FALSE)
@@ -621,7 +779,6 @@
      *  dynamically updated to true, the Parent Box's icon should win for its entire surface area,
      *  including within Child Box.
      */
-    @Ignore("b/266976920 - not yet implemented")
     @Test
     fun parentChildPartialOverlap_parentOverrideDescendantsDynamicallyUpdated() {
         val parentOverrideState = mutableStateOf(false)
@@ -667,6 +824,180 @@
 
     /**
      * Setup:
+     * The hierarchy for the initial setup of this test is:
+     *  Parent Box (custom icon = [PointerIcon.Crosshair], overrideDescendants = FALSE)
+     *      ⤷ Child Box (custom icon = [PointerIcon.Text], overrideDescendants = FALSE)
+     *
+     *  After hovering over various parts of the screen and verify the results, we update the
+     *  parent's overrideDescendants to true:
+     *  Parent Box (custom icon = [PointerIcon.Crosshair], overrideDescendants = TRUE)
+     *    ⤷ Child Box (custom icon = [PointerIcon.Text], overrideDescendants = FALSE)
+     *
+     *  After several assertions, it reverts back to false in the parent:
+     *  Parent Box (custom icon = [PointerIcon.Crosshair], overrideDescendants = FALSE)
+     *    ⤷ Child Box (custom icon = [PointerIcon.Text], overrideDescendants = FALSE)
+     *
+     *
+     *  Expected Output:
+     *  Initially, the Child Box's [PointerIcon.Text] should win for its entire surface area
+     *  because the parent does not override descendants. After the Parent Box dynamically changes
+     *  overrideDescendants to true, the Parent Box's [PointerIcon.Crosshair] should win for the
+     *  child's surface area within the Parent Box BUT NOT the portion of the Child Box that is
+     *  outside the Parent Box.
+     *
+     *  It should then revert back to Child Box's [PointerIcon.Text] (in all scenarios) after the
+     *  Parent Box's overrideDescendants is set back to false.
+     *
+     */
+    @Test
+    fun parentChildPartialOverlapAndExtendsBeyondParent_dynamicOverrideDescendants() {
+        var parentOverrideDescendants by mutableStateOf(false)
+        rule.setContent {
+            CompositionLocalProvider(LocalPointerIconService provides iconService) {
+
+                Box(
+                    modifier = Modifier
+                        .requiredSize(300.dp)
+                        .border(BorderStroke(2.dp, SolidColor(Color.Green)))
+
+                ) {
+                    // This child extends beyond the borders of the parent (enabling this test)
+                    Box(
+                        modifier = Modifier
+                            .size(150.dp)
+                            .border(BorderStroke(2.dp, SolidColor(Color.Red)))
+                            .testTag(parentIconTag)
+                            .then(
+                                Modifier.pointerHoverIcon(
+                                    desiredParentIcon,
+                                    overrideDescendants = parentOverrideDescendants
+                                )
+                            )
+
+                    ) {
+                        Box(
+                            Modifier
+                                .padding(20.dp)
+                                .offset(100.dp)
+                                .width(300.dp)
+                                .height(100.dp)
+                                .border(BorderStroke(2.dp, SolidColor(Color.Black)))
+                                .testTag(childIconTag)
+                                .pointerHoverIcon(desiredChildIcon, overrideDescendants = false)
+                        )
+                    }
+                }
+            }
+        }
+        // Verify initial state of pointer icon
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredDefaultIcon)
+        }
+        // Hover over Child Box and verify it has the desired child icon (outside parent)
+        rule.onNodeWithTag(childIconTag).performMouseInput {
+            enter(bottomRight)
+        }
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredChildIcon)
+        }
+
+        // Hover over Child Box and verify it has the desired child icon (inside parent)
+        rule.onNodeWithTag(childIconTag).performMouseInput {
+            moveTo(bottomLeft)
+        }
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredChildIcon)
+        }
+
+        // Move to Parent Box and verify its icon is the desired parent icon
+        rule.onNodeWithTag(parentIconTag).performMouseInput {
+            moveTo(bottomRight)
+        }
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredParentIcon)
+        }
+        // Move back to the Child Box (portion inside parent)
+        rule.onNodeWithTag(childIconTag).performMouseInput {
+            moveTo(bottomLeft)
+        }
+
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredChildIcon)
+        }
+
+        // Dynamically change the pointerHoverIcon Modifier of the Parent Box to
+        // override descendants.
+        rule.runOnIdle {
+            parentOverrideDescendants = true
+        }
+
+        // Verify the Child Box has updated to respect the desired parent icon
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredParentIcon)
+        }
+
+        // Hover over Child Box and verify it has the desired child icon (outside parent)
+        rule.onNodeWithTag(childIconTag).performMouseInput {
+            moveTo(bottomRight)
+        }
+
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredChildIcon)
+        }
+
+        // Move to the Parent Box and verify it also has the desired parent icon
+        rule.onNodeWithTag(parentIconTag).performMouseInput {
+            moveTo(bottomRight)
+        }
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredParentIcon)
+        }
+
+        // Move within the Child Box (portion inside parent) and verify it is still
+        // respecting the desired parent icon
+        rule.onNodeWithTag(childIconTag).performMouseInput {
+            moveTo(bottomLeft)
+        }
+
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredParentIcon)
+        }
+
+        // Dynamically change the pointerHoverIcon Modifier of the Parent Box to NOT
+        // override descendants.
+        rule.runOnIdle {
+            parentOverrideDescendants = false
+        }
+
+        // Verify it's changed to child icon
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredChildIcon)
+        }
+
+        // Move to Parent Box and verify its icon is the desired parent icon
+        rule.onNodeWithTag(parentIconTag).performMouseInput {
+            moveTo(bottomRight)
+        }
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredParentIcon)
+        }
+        // Move back to the Child Box
+        rule.onNodeWithTag(childIconTag).performMouseInput {
+            moveTo(bottomLeft)
+        }
+
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredChildIcon)
+        }
+
+        // Exit hovering over Parent Box
+        rule.onNodeWithTag(parentIconTag).performMouseInput {
+            exit()
+        }
+    }
+
+    /**
+     * Setup:
      * The hierarchy for this test is setup as:
      *  Parent Box (no custom icon set)
      *    ⤷ ChildA Box (custom icon = [PointerIcon.Text], overrideDescendants = FALSE)
@@ -4032,6 +4363,101 @@
         }
     }
 
+    @Test
+    fun resetPointerIconWhenChildRemoved_parentDoesSetIcon_iconIsHand() {
+        val defaultIconTag = "myDefaultWrapper"
+        var show by mutableStateOf(true)
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalPointerIconService provides iconService
+            ) {
+                Box(modifier = Modifier
+                    .fillMaxSize()
+                    .pointerHoverIcon(PointerIcon.Hand)
+                    .testTag(defaultIconTag)
+                ) {
+                    if (show) {
+                        Box(
+                            modifier = Modifier
+                                .pointerHoverIcon(PointerIcon.Text)
+                                .size(10.dp, 10.dp)
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            // No mouse movement yet, should be default
+            assertThat(iconService.getIcon()).isEqualTo(PointerIcon.Default)
+        }
+
+        rule.onNodeWithTag(defaultIconTag).performMouseInput {
+            moveTo(Offset(x = 5f, y = 5f))
+        }
+
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(PointerIcon.Text)
+        }
+
+        show = false
+
+        rule.onNodeWithTag(defaultIconTag).performMouseInput {
+            moveTo(Offset(x = 6f, y = 6f))
+        }
+
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(PointerIcon.Hand)
+        }
+    }
+
+    @Test
+    fun resetPointerIconWhenChildRemoved_parentDoesNotSetIcon_iconIsDefault() {
+        val defaultIconTag = "myDefaultWrapper"
+        var show by mutableStateOf(true)
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalPointerIconService provides iconService
+            ) {
+                Box(modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(defaultIconTag)
+                ) {
+                    if (show) {
+                        Box(
+                            modifier = Modifier
+                                .pointerHoverIcon(PointerIcon.Text)
+                                .size(10.dp, 10.dp)
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            // No mouse movement yet, should be default
+            assertThat(iconService.getIcon()).isEqualTo(PointerIcon.Default)
+        }
+
+        rule.onNodeWithTag(defaultIconTag).performMouseInput {
+            moveTo(Offset(x = 5f, y = 5f))
+        }
+
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(PointerIcon.Text)
+        }
+
+        show = false
+
+        rule.onNodeWithTag(defaultIconTag).performMouseInput {
+            moveTo(Offset(x = 6f, y = 6f))
+        }
+
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(PointerIcon.Default)
+        }
+    }
+
     private fun verifyIconOnHover(tag: String, expectedIcon: PointerIcon) {
         // Hover over element with specified tag
         rule.onNodeWithTag(tag).performMouseInput {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
index a9970b5..5d2266e 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessorTest.kt
@@ -155,6 +155,7 @@
                 uptime = index.toLong(),
                 positionOnScreen = Offset(offset.x + index, offset.y + index),
                 position = Offset(offset.x + index, offset.y + index),
+                originalEventPosition = Offset(offset.x + index, offset.y + index),
                 down = true,
                 pressure = 1.0f,
                 type = pointerType
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/TraversableModifierNodeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/TraversableModifierNodeTest.kt
new file mode 100644
index 0000000..0fb4c18
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/TraversableModifierNodeTest.kt
@@ -0,0 +1,1706 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.node
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TraversableModifierNodeTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    private lateinit var parentNode: ClassOneWithSharedKeyTraversalNode
+
+    private lateinit var childA: ClassOneWithSharedKeyTraversalNode
+    private lateinit var childB: ClassTwoWithSharedKeyTraversalNode
+    private lateinit var childC: ClassThreeWithOtherKeyTraversalNode
+
+    private lateinit var grandChildNodeA: ClassOneWithSharedKeyTraversalNode
+    private lateinit var grandChildNodeB: ClassTwoWithSharedKeyTraversalNode
+    private lateinit var grandChildNodeC: ClassThreeWithOtherKeyTraversalNode
+
+    private lateinit var grandChildNodeD: ClassOneWithSharedKeyTraversalNode
+    private lateinit var grandChildNodeF: ClassThreeWithOtherKeyTraversalNode
+
+    private lateinit var grandChildNodeG: ClassOneWithSharedKeyTraversalNode
+
+    /**
+     * The UI hierarchy for this test is setup as:
+     *
+     *  Parent Column (ClassOneWithSharedKeyTraversalNode)
+     *    ⤷ ChildA Row (ClassOneWithSharedKeyTraversalNode)
+     *        ⤷ GrandchildA Box (ClassOneWithSharedKeyTraversalNode)
+     *        ⤷ GrandchildB Box (ClassTwoWithSharedKeyTraversalNode)
+     *        ⤷ GrandchildC Box (ClassThreeWithOtherKeyTraversalNode)
+     *
+     *    ⤷ ChildB Row (ClassTwoWithSharedKeyTraversalNode)
+     *         ⤷ GrandchildD Box (ClassOneWithSharedKeyTraversalNode)
+     *         ⤷ GrandchildE Box (ClassTwoWithSharedKeyTraversalNode)
+     *         ⤷ GrandchildF Box (ClassThreeWithOtherKeyTraversalNode)
+     *
+     *    ⤷ ChildC Row (ClassThreeWithOtherKeyTraversalNode)
+     *         ⤷ GrandchildG Box (ClassOneWithSharedKeyTraversalNode)
+     *         ⤷ GrandchildH Box (ClassTwoWithSharedKeyTraversalNode)
+     *         ⤷ GrandchildI Box (ClassThreeWithOtherKeyTraversalNode)
+     *
+     *    ⤷ ChildD Row (ClassTwoWithSharedKeyTraversalNode)
+     *         ⤷ GrandchildJ Box (ClassOneWithSharedKeyTraversalNode)
+     *
+     */
+    @Composable
+    private fun createUi() {
+        Column(
+            modifier = Modifier
+                .fillMaxSize()
+                .background(Color.Red)
+                .testTraversalNodeClassOneWithSharedKey("Parent") {
+                    parentNode = this
+                },
+        ) {
+            // Child A
+            Row(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .background(Color.Green)
+                    .testTraversalNodeClassOneWithSharedKey("Child_A") {
+                        childA = this
+                    }
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(30.dp)
+                        .background(Color.Blue)
+                        .testTraversalNodeClassOneWithSharedKey("Grandchild_A") {
+                            grandChildNodeA = this
+                        }
+                ) { }
+                Box(
+                    modifier = Modifier
+                        .size(30.dp)
+                        .background(Color.White)
+                        .testTraversalNodeClassTwoWithSharedKey("Grandchild_B") {
+                            grandChildNodeB = this
+                        }
+                ) { }
+                Box(
+                    modifier = Modifier
+                        .size(30.dp)
+                        .background(Color.Black)
+                        .testTraversalNodeClassThreeWithOtherKey("Grandchild_C") {
+                            grandChildNodeC = this
+                        }
+                ) { }
+            }
+            // Child B
+            Row(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .background(Color.Magenta)
+                    .testTraversalNodeClassTwoWithSharedKey("Child_B") {
+                        childB = this
+                    }
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(30.dp)
+                        .background(Color.Yellow)
+                        .testTraversalNodeClassOneWithSharedKey("Grandchild_D") {
+                            grandChildNodeD = this
+                        }
+                ) { }
+                Box(
+                    modifier = Modifier
+                        .size(30.dp)
+                        .background(Color.Blue)
+                        .testTraversalNodeClassTwoWithSharedKey("Grandchild_E")
+                ) { }
+                Box(
+                    modifier = Modifier
+                        .size(30.dp)
+                        .background(Color.Gray)
+                        .testTraversalNodeClassThreeWithOtherKey("Grandchild_F") {
+                            grandChildNodeF = this
+                        }
+                ) { }
+            }
+            // Child C
+            Row(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .background(Color.Cyan)
+                    .testTraversalNodeClassThreeWithOtherKey("Child_C") {
+                        childC = this
+                    }
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(30.dp)
+                        .background(Color.Blue)
+                        .testTraversalNodeClassOneWithSharedKey("Grandchild_G") {
+                            grandChildNodeG = this
+                        }
+                ) { }
+                Box(
+                    modifier = Modifier
+                        .size(30.dp)
+                        .background(Color.Magenta)
+                        .testTraversalNodeClassTwoWithSharedKey("Grandchild_H")
+                ) { }
+                Box(
+                    modifier = Modifier
+                        .size(30.dp)
+                        .background(Color.Black)
+                        .testTraversalNodeClassThreeWithOtherKey("Grandchild_I")
+                ) { }
+            }
+
+            // Child D
+            Row(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .background(Color.Green)
+                    .testTraversalNodeClassTwoWithSharedKey("Child_D")
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(30.dp)
+                        .background(Color.Black)
+                        .testTraversalNodeClassOneWithSharedKey("Grandchild_J")
+                ) { }
+            }
+        }
+    }
+
+    @Before
+    fun setup() {
+        rule.setContent {
+            createUi()
+        }
+    }
+
+    // *********** Nearest Traversable Ancestor Tests ***********
+    @Test
+    fun nearestTraversableAncestor_ancestorsWithTheSameClass() {
+        var nearestAncestorNode: TraversableNode? = null
+
+        // Starts at grandchild A (which has a parent and grandparent of the same class)
+        rule.runOnIdle {
+            nearestAncestorNode = grandChildNodeA.nearestTraversableAncestor()
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(nearestAncestorNode).isEqualTo(childA)
+        }
+
+        // Starts at grandchild D (which has a parent of a different class + same key and
+        // grandparent of the same class).
+        nearestAncestorNode = null
+
+        rule.runOnIdle {
+            nearestAncestorNode = grandChildNodeD.nearestTraversableAncestor()
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(nearestAncestorNode).isEqualTo(parentNode)
+        }
+
+        // Starts at grandchild G (which has a parent of a different class + different key and
+        // a grandparent of the same class).
+        nearestAncestorNode = null
+
+        rule.runOnIdle {
+            nearestAncestorNode = grandChildNodeG.nearestTraversableAncestor()
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(nearestAncestorNode).isEqualTo(parentNode)
+        }
+    }
+
+    @Test
+    fun nearestTraversableAncestor_ancestorsWithOutTheSameClass() {
+        var nearestAncestorNode: TraversableNode? = null
+
+        // Starts at grandchild B (which has a parent and grandparent of different class but the
+        // same key). Neither should match.
+        rule.runOnIdle {
+            nearestAncestorNode = grandChildNodeB.nearestTraversableAncestor()
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(nearestAncestorNode).isEqualTo(null)
+        }
+
+        nearestAncestorNode = null
+
+        // Starts at grandchild C (which has a parent and grandparent of different class and a
+        // different key). Neither should match.
+        rule.runOnIdle {
+            nearestAncestorNode = grandChildNodeC.nearestTraversableAncestor()
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(nearestAncestorNode).isEqualTo(null)
+        }
+    }
+
+    @Test
+    fun nearestTraversableAncestorWithKey_ancestorsWithTheSameKey() {
+        var nearestAncestorNode: TraversableNode? = null
+
+        // Starts from grandchild A with SHARED_TRAVERSAL_NODE_KEY.
+        rule.runOnIdle {
+            nearestAncestorNode =
+                grandChildNodeA.nearestTraversableAncestorWithKey(SHARED_TRAVERSAL_NODE_KEY)
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(nearestAncestorNode).isEqualTo(childA)
+        }
+
+        nearestAncestorNode = null
+
+        // Starts from grandchild D with SHARED_TRAVERSAL_NODE_KEY.
+        rule.runOnIdle {
+            nearestAncestorNode =
+                grandChildNodeD.nearestTraversableAncestorWithKey(SHARED_TRAVERSAL_NODE_KEY)
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(nearestAncestorNode).isEqualTo(childB)
+        }
+
+        nearestAncestorNode = null
+
+        // Starts from grandchild G with SHARED_TRAVERSAL_NODE_KEY.
+        rule.runOnIdle {
+            nearestAncestorNode =
+                grandChildNodeG.nearestTraversableAncestorWithKey(SHARED_TRAVERSAL_NODE_KEY)
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(nearestAncestorNode).isEqualTo(parentNode)
+        }
+
+        nearestAncestorNode = null
+
+        // Starts from grandchild G with OTHER_TRAVERSAL_NODE_KEY.
+        rule.runOnIdle {
+            nearestAncestorNode =
+                grandChildNodeG.nearestTraversableAncestorWithKey(OTHER_TRAVERSAL_NODE_KEY)
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(nearestAncestorNode).isEqualTo(childC)
+        }
+    }
+
+    @Test
+    fun nearestTraversableAncestorWithKey_ancestorsWithoutTheSameKey() {
+        var nearestAncestorNode: TraversableNode? = null
+
+        // Starts from grandchild A with OTHER_TRAVERSAL_NODE_KEY.
+        rule.runOnIdle {
+            nearestAncestorNode =
+                grandChildNodeA.nearestTraversableAncestorWithKey(OTHER_TRAVERSAL_NODE_KEY)
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(nearestAncestorNode).isEqualTo(null)
+        }
+
+        nearestAncestorNode = null
+
+        // Starts from grandchild B with OTHER_TRAVERSAL_NODE_KEY.
+        rule.runOnIdle {
+            nearestAncestorNode =
+                grandChildNodeB.nearestTraversableAncestorWithKey(OTHER_TRAVERSAL_NODE_KEY)
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(nearestAncestorNode).isEqualTo(null)
+        }
+
+        nearestAncestorNode = null
+
+        // Starts from grandchild C with OTHER_TRAVERSAL_NODE_KEY.
+        rule.runOnIdle {
+            nearestAncestorNode =
+                grandChildNodeC.nearestTraversableAncestorWithKey(OTHER_TRAVERSAL_NODE_KEY)
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(nearestAncestorNode).isEqualTo(null)
+        }
+
+        nearestAncestorNode = null
+
+        // Starts from grandchild F with OTHER_TRAVERSAL_NODE_KEY.
+        rule.runOnIdle {
+            nearestAncestorNode =
+                grandChildNodeF.nearestTraversableAncestorWithKey(OTHER_TRAVERSAL_NODE_KEY)
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(nearestAncestorNode).isEqualTo(null)
+        }
+    }
+
+    @Test
+    fun nearestTraversableAncestorWithKey_nullKey() {
+        var nearestAncestorNode: TraversableNode? = null
+
+        // Starts from grandchild A with null key.
+        rule.runOnIdle {
+            nearestAncestorNode = grandChildNodeA.nearestTraversableAncestorWithKey(null)
+        }
+
+        rule.runOnIdle {
+            // No ancestors have a key of null
+            Truth.assertThat(nearestAncestorNode).isEqualTo(null)
+        }
+
+        // Starts from grandchild D with null key.
+        nearestAncestorNode = null
+
+        rule.runOnIdle {
+            nearestAncestorNode = grandChildNodeD.nearestTraversableAncestorWithKey(null)
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(nearestAncestorNode).isEqualTo(null)
+        }
+
+        // Starts from grandchild F with null key.
+        nearestAncestorNode = null
+
+        rule.runOnIdle {
+            nearestAncestorNode = grandChildNodeF.nearestTraversableAncestorWithKey(null)
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(nearestAncestorNode).isEqualTo(null)
+        }
+    }
+
+    // *********** Traverse Ancestors Tests ***********
+    @Test
+    fun traverseAncestors_sameClass() {
+        var sameClassAncestors = 0
+
+        // Starts from grandchild (which has a parent and grandparent of the same class).
+        rule.runOnIdle {
+            grandChildNodeA.traverseAncestors {
+                sameClassAncestors++
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(sameClassAncestors).isEqualTo(2)
+        }
+
+        // Starts at grandchild D (which has a parent of a different class + same key and
+        // grandparent of the same class).
+        sameClassAncestors = 0
+
+        rule.runOnIdle {
+            grandChildNodeD.traverseAncestors {
+                sameClassAncestors++
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(sameClassAncestors).isEqualTo(1)
+        }
+
+        // Starts at grandchild G (which has a parent of a different class + different key and
+        // a grandparent of the same class).
+        sameClassAncestors = 0
+
+        rule.runOnIdle {
+            grandChildNodeG.traverseAncestors {
+                sameClassAncestors++
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(sameClassAncestors).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun traverseAncestors_sameClassWithCancellation() {
+        var sameClassAncestors = 0
+
+        // Starts at grandchild A (which has a parent and grandparent of the same class).
+        rule.runOnIdle {
+            grandChildNodeA.traverseAncestors {
+                sameClassAncestors++
+                // Cancel traversal
+                false
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(sameClassAncestors).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun traverseAncestorsWithKey_sameKey() {
+        var totalMatchingAncestors = 0
+        var classOneWithSharedKeyTraversalNodeAncestors = 0
+        var classTwoWithSharedKeyTraversalNodeAncestors = 0
+        var classThreeWithOtherKeyTraversalNodeAncestors = 0
+
+        // Starts at grandchild A (which has a parent and grandparent of the same class).
+        rule.runOnIdle {
+            grandChildNodeA.traverseAncestorsWithKey(SHARED_TRAVERSAL_NODE_KEY) {
+                totalMatchingAncestors++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        classOneWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        classTwoWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        classThreeWithOtherKeyTraversalNodeAncestors++
+                    }
+                }
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingAncestors).isEqualTo(2)
+            Truth.assertThat(classOneWithSharedKeyTraversalNodeAncestors).isEqualTo(2)
+            Truth.assertThat(classTwoWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            Truth.assertThat(classThreeWithOtherKeyTraversalNodeAncestors).isEqualTo(0)
+        }
+
+        // Starts at grandchild D (which has a parent of a different class + same key and
+        // grandparent of the same class).
+        totalMatchingAncestors = 0
+        classOneWithSharedKeyTraversalNodeAncestors = 0
+        classTwoWithSharedKeyTraversalNodeAncestors = 0
+        classThreeWithOtherKeyTraversalNodeAncestors = 0
+
+        rule.runOnIdle {
+            grandChildNodeD.traverseAncestorsWithKey(SHARED_TRAVERSAL_NODE_KEY) {
+                totalMatchingAncestors++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        classOneWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        classTwoWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        classThreeWithOtherKeyTraversalNodeAncestors++
+                    }
+                }
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingAncestors).isEqualTo(2)
+            Truth.assertThat(classOneWithSharedKeyTraversalNodeAncestors).isEqualTo(1)
+            Truth.assertThat(classTwoWithSharedKeyTraversalNodeAncestors).isEqualTo(1)
+            Truth.assertThat(classThreeWithOtherKeyTraversalNodeAncestors).isEqualTo(0)
+        }
+
+        // Starts at grandchild G (which has a parent of a different class + different key and
+        // a grandparent of the same class).
+        totalMatchingAncestors = 0
+        classOneWithSharedKeyTraversalNodeAncestors = 0
+        classTwoWithSharedKeyTraversalNodeAncestors = 0
+        classThreeWithOtherKeyTraversalNodeAncestors = 0
+
+        rule.runOnIdle {
+            grandChildNodeG.traverseAncestorsWithKey(SHARED_TRAVERSAL_NODE_KEY) {
+                totalMatchingAncestors++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        classOneWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        classTwoWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        classThreeWithOtherKeyTraversalNodeAncestors++
+                    }
+                }
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingAncestors).isEqualTo(1)
+            Truth.assertThat(classOneWithSharedKeyTraversalNodeAncestors).isEqualTo(1)
+            Truth.assertThat(classTwoWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            Truth.assertThat(classThreeWithOtherKeyTraversalNodeAncestors).isEqualTo(0)
+        }
+
+        // Starts at grandchild G (which has a parent of OTHER_TRAVERSAL_NODE_KEY and
+        // a grandparent without OTHER_TRAVERSAL_NODE_KEY.).
+        totalMatchingAncestors = 0
+        classOneWithSharedKeyTraversalNodeAncestors = 0
+        classTwoWithSharedKeyTraversalNodeAncestors = 0
+        classThreeWithOtherKeyTraversalNodeAncestors = 0
+
+        rule.runOnIdle {
+            grandChildNodeG.traverseAncestorsWithKey(OTHER_TRAVERSAL_NODE_KEY) {
+                totalMatchingAncestors++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        classOneWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        classTwoWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        classThreeWithOtherKeyTraversalNodeAncestors++
+                    }
+                }
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingAncestors).isEqualTo(1)
+            Truth.assertThat(classOneWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            Truth.assertThat(classTwoWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            Truth.assertThat(classThreeWithOtherKeyTraversalNodeAncestors).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun traverseAncestorsWithKey_differentKeyFromCallingNode() {
+        var totalMatchingAncestors = 0
+        var classOneWithSharedKeyTraversalNodeAncestors = 0
+        var classTwoWithSharedKeyTraversalNodeAncestors = 0
+        var classThreeWithOtherKeyTraversalNodeAncestors = 0
+
+        // Starts at grandchild A (which has a parent and grandparent with keys other than
+        // OTHER_TRAVERSAL_NODE_KEY.
+        rule.runOnIdle {
+            grandChildNodeA.traverseAncestorsWithKey(OTHER_TRAVERSAL_NODE_KEY) {
+                totalMatchingAncestors++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        classOneWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        classTwoWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        classThreeWithOtherKeyTraversalNodeAncestors++
+                    }
+                }
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingAncestors).isEqualTo(0)
+            Truth.assertThat(classOneWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            Truth.assertThat(classTwoWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            Truth.assertThat(classThreeWithOtherKeyTraversalNodeAncestors).isEqualTo(0)
+        }
+
+        // Starts at grandchild D (which has a parent and grandparent with keys other than
+        // OTHER_TRAVERSAL_NODE_KEY.
+        totalMatchingAncestors = 0
+        classOneWithSharedKeyTraversalNodeAncestors = 0
+        classTwoWithSharedKeyTraversalNodeAncestors = 0
+        classThreeWithOtherKeyTraversalNodeAncestors = 0
+
+        rule.runOnIdle {
+            grandChildNodeD.traverseAncestorsWithKey(OTHER_TRAVERSAL_NODE_KEY) {
+                totalMatchingAncestors++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        classOneWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        classTwoWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        classThreeWithOtherKeyTraversalNodeAncestors++
+                    }
+                }
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingAncestors).isEqualTo(0)
+            Truth.assertThat(classOneWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            Truth.assertThat(classTwoWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            Truth.assertThat(classThreeWithOtherKeyTraversalNodeAncestors).isEqualTo(0)
+        }
+    }
+
+    // Matches only keys that are set to null (of which there are none).
+    @Test
+    fun traverseAncestorsWithKey_nullKey() {
+        var totalMatchingAncestors = 0
+        var sameClassAncestors = 0
+        var sameKeyDifferentClassAncestors = 0
+        var differentKeyDifferentClassAncestors = 0
+
+        // Starts at grandchild A (which has a parent and grandparent of the same class).
+        rule.runOnIdle {
+            grandChildNodeA.traverseAncestorsWithKey(null) {
+                totalMatchingAncestors++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        sameClassAncestors++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        sameKeyDifferentClassAncestors++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        differentKeyDifferentClassAncestors++
+                    }
+                }
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingAncestors).isEqualTo(0)
+            Truth.assertThat(sameClassAncestors).isEqualTo(0)
+            Truth.assertThat(sameKeyDifferentClassAncestors).isEqualTo(0)
+            Truth.assertThat(differentKeyDifferentClassAncestors).isEqualTo(0)
+        }
+
+        // Starts at grandchild D (which has a parent of a different class + same key and
+        // grandparent of the same class).
+        totalMatchingAncestors = 0
+        sameClassAncestors = 0
+        sameKeyDifferentClassAncestors = 0
+        differentKeyDifferentClassAncestors = 0
+
+        rule.runOnIdle {
+            grandChildNodeD.traverseAncestorsWithKey(null) {
+                totalMatchingAncestors++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        sameClassAncestors++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        sameKeyDifferentClassAncestors++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        differentKeyDifferentClassAncestors++
+                    }
+                }
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingAncestors).isEqualTo(0)
+            Truth.assertThat(sameClassAncestors).isEqualTo(0)
+            Truth.assertThat(sameKeyDifferentClassAncestors).isEqualTo(0)
+            Truth.assertThat(differentKeyDifferentClassAncestors).isEqualTo(0)
+        }
+
+        // Starts at grandchild G (which has a parent of a different class + different key and
+        // a grandparent of the same class).
+        totalMatchingAncestors = 0
+        sameClassAncestors = 0
+        sameKeyDifferentClassAncestors = 0
+        differentKeyDifferentClassAncestors = 0
+
+        rule.runOnIdle {
+            grandChildNodeG.traverseAncestorsWithKey(null) {
+                totalMatchingAncestors++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        sameClassAncestors++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        sameKeyDifferentClassAncestors++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        differentKeyDifferentClassAncestors++
+                    }
+                }
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingAncestors).isEqualTo(0)
+            Truth.assertThat(sameClassAncestors).isEqualTo(0)
+            Truth.assertThat(sameKeyDifferentClassAncestors).isEqualTo(0)
+            Truth.assertThat(differentKeyDifferentClassAncestors).isEqualTo(0)
+        }
+    }
+
+    // *********** Traverse Children Tests ***********
+    @Test
+    fun traverseChildren_sameClass() {
+        var sameClassChildren = 0
+
+        rule.runOnIdle {
+            parentNode.traverseChildren {
+                sameClassChildren++
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(sameClassChildren).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun traverseChildrenWithKey_sameKey() {
+        var totalMatchingChildren = 0
+        // All these are in relation to the parent class where we run the traversal.
+        var sameClassChildren = 0
+        var sameKeyDifferentClassChildren = 0
+
+        rule.runOnIdle {
+            parentNode.traverseChildrenWithKey(SHARED_TRAVERSAL_NODE_KEY) {
+                totalMatchingChildren++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        sameClassChildren++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        sameKeyDifferentClassChildren++
+                    }
+                }
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingChildren).isEqualTo(3)
+            Truth.assertThat(sameClassChildren).isEqualTo(1)
+            Truth.assertThat(sameKeyDifferentClassChildren).isEqualTo(2)
+        }
+    }
+
+    @Test
+    fun traverseChildrenWithKey_differentKeyFromCallingNode() {
+        var totalMatchingChildren = 0
+        var classOneWithSharedKeyTraversalNodeAncestors = 0
+        var classTwoWithSharedKeyTraversalNodeAncestors = 0
+        // Only class with key = OTHER_TRAVERSAL_NODE_KEY.
+        var classThreeWithOtherKeyTraversalNodeAncestors = 0
+
+        rule.runOnIdle {
+            parentNode.traverseChildrenWithKey(OTHER_TRAVERSAL_NODE_KEY) {
+                totalMatchingChildren++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        classOneWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        classTwoWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        classThreeWithOtherKeyTraversalNodeAncestors++
+                    }
+                }
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingChildren).isEqualTo(1)
+            Truth.assertThat(classOneWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            Truth.assertThat(classTwoWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            Truth.assertThat(classThreeWithOtherKeyTraversalNodeAncestors).isEqualTo(1)
+        }
+    }
+
+    // Matches only keys that are set to null (of which there are none).
+    @Test
+    fun traverseChildrenWithKey_nullKey() {
+        var totalMatchingChildren = 0
+        var classOneWithSharedKeyTraversalNodeAncestors = 0
+        var classTwoWithSharedKeyTraversalNodeAncestors = 0
+        var classThreeWithOtherKeyTraversalNodeAncestors = 0
+
+        rule.runOnIdle {
+            // parentNode is of type ClassOneWithSharedKeyTraversalNode
+            parentNode.traverseChildrenWithKey(null) {
+                totalMatchingChildren++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        classOneWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        classTwoWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        classThreeWithOtherKeyTraversalNodeAncestors++
+                    }
+                }
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingChildren).isEqualTo(0)
+            Truth.assertThat(classOneWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            Truth.assertThat(classTwoWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            Truth.assertThat(classThreeWithOtherKeyTraversalNodeAncestors).isEqualTo(0)
+        }
+    }
+
+    // *********** Traverse Subtree Tests ***********
+    @Test
+    fun traverseSubtree_sameClass() {
+        var sameClassNodes = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtree {
+                sameClassNodes++
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(sameClassNodes).isEqualTo(5)
+        }
+    }
+
+    @Test
+    fun traverseSubtreeWithKey_fromParentWithSameKey() {
+        var totalMatchingNodes = 0
+        var sameClassNodes = 0
+        var sameKeyDifferentClassNodes = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeWithKey(SHARED_TRAVERSAL_NODE_KEY) {
+                totalMatchingNodes++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        sameClassNodes++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        sameKeyDifferentClassNodes++
+                    }
+                }
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingNodes).isEqualTo(10)
+            Truth.assertThat(sameClassNodes).isEqualTo(5)
+            Truth.assertThat(sameKeyDifferentClassNodes).isEqualTo(5)
+        }
+    }
+
+    @Test
+    fun traverseSubtreeWithKey_differentKeyFromCallingNode() {
+        var totalMatchingNodes = 0
+        var classOneWithSharedKeyTraversalNodeAncestors = 0
+        var classTwoWithSharedKeyTraversalNodeAncestors = 0
+        // Only class with key = OTHER_TRAVERSAL_NODE_KEY.
+        var classThreeWithOtherKeyTraversalNodeAncestors = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeWithKey(OTHER_TRAVERSAL_NODE_KEY) {
+                totalMatchingNodes++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        classOneWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        classTwoWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        classThreeWithOtherKeyTraversalNodeAncestors++
+                    }
+                }
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingNodes).isEqualTo(4)
+            // Should be zero because it won't match the shared key
+            Truth.assertThat(classOneWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            // Should be zero because it won't match the shared key
+            Truth.assertThat(classTwoWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            Truth.assertThat(classThreeWithOtherKeyTraversalNodeAncestors).isEqualTo(4)
+        }
+    }
+
+    // Matches only keys that are set to null (of which there are none).
+    @Test
+    fun traverseSubtreeWithKey_nullKey() {
+        var totalMatchingNodes = 0
+        // All these are in relation to the parent class where we run the traversal.
+        var sameClassNodes = 0
+        var sameKeyDifferentClassNodes = 0
+        var differentKeyDifferentClassNodes = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeWithKey(null) {
+                totalMatchingNodes++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        sameClassNodes++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        sameKeyDifferentClassNodes++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        differentKeyDifferentClassNodes++
+                    }
+                }
+                // Continue traversal
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingNodes).isEqualTo(0)
+            Truth.assertThat(sameClassNodes).isEqualTo(0)
+            Truth.assertThat(sameKeyDifferentClassNodes).isEqualTo(0)
+            Truth.assertThat(differentKeyDifferentClassNodes).isEqualTo(0)
+        }
+    }
+
+    // *********** Traverse Subtree If Tests ***********
+    @Test
+    fun traverseSubtreeIf_alwaysContinueTraversal() {
+        var sameClassNodes = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeIf {
+                sameClassNodes++
+                VisitSubtreeIfAction.VisitSubtree
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(sameClassNodes).isEqualTo(5)
+        }
+    }
+
+    @Test
+    fun traverseSubtreeIf_alwaysSkipSubtree() {
+        var sameClassNodes = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeIf {
+                sameClassNodes++
+                VisitSubtreeIfAction.SkipSubtree
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(sameClassNodes).isEqualTo(4)
+        }
+    }
+
+    @Test
+    fun traverseSubtreeIf_skipOneSubtree() {
+        var sameClassNodes = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeIf {
+                sameClassNodes++
+
+                // This will skip the subtree under childA, thus remove the grandchildA of
+                // ClassOneWithSharedKeyTraversalNode from the count
+                if (it == childA) {
+                    VisitSubtreeIfAction.SkipSubtree
+                } else {
+                    VisitSubtreeIfAction.VisitSubtree
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(sameClassNodes).isEqualTo(4)
+        }
+    }
+
+    @Test
+    fun traverseSubtreeWithKey_sameKeyFromCallingNode_alwaysContinueTraversal() {
+        var totalMatchingNodes = 0
+        // All these are in relation to the parent class where we run the traversal.
+        var sameClassNodes = 0
+        var sameKeyDifferentClassNodes = 0
+        var otherNodes = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeIfWithKey(SHARED_TRAVERSAL_NODE_KEY) {
+                totalMatchingNodes++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        sameClassNodes++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        sameKeyDifferentClassNodes++
+                    }
+
+                    else -> {
+                        otherNodes++
+                    }
+                }
+                VisitSubtreeIfAction.VisitSubtree
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingNodes).isEqualTo(10)
+            Truth.assertThat(sameClassNodes).isEqualTo(5)
+            Truth.assertThat(sameKeyDifferentClassNodes).isEqualTo(5)
+            Truth.assertThat(otherNodes).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun traverseSubtreeWithKey_sameKeyFromCallingNode_alwaysCancelTraversal() {
+        var totalMatchingNodes = 0
+        // All these are in relation to the parent class where we run the traversal.
+        var sameClassNodes = 0
+        var sameKeyDifferentClassNodes = 0
+        var otherNodes = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeIfWithKey(SHARED_TRAVERSAL_NODE_KEY) {
+                totalMatchingNodes++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        sameClassNodes++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        sameKeyDifferentClassNodes++
+                    }
+
+                    else -> {
+                        otherNodes++
+                    }
+                }
+                VisitSubtreeIfAction.CancelTraversal
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingNodes).isEqualTo(1)
+            Truth.assertThat(sameClassNodes).isEqualTo(1)
+            Truth.assertThat(sameKeyDifferentClassNodes).isEqualTo(0)
+            Truth.assertThat(otherNodes).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun traverseSubtreeWithKey_sameKeyFromCallingNode_alwaysSkipSubtree() {
+        var totalMatchingNodes = 0
+        // All these are in relation to the parent class where we run the traversal.
+        var sameClassNodes = 0
+        var sameKeyDifferentClassNodes = 0
+        var otherNodes = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeIfWithKey(SHARED_TRAVERSAL_NODE_KEY) {
+                totalMatchingNodes++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        sameClassNodes++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        sameKeyDifferentClassNodes++
+                    }
+
+                    else -> {
+                        otherNodes++
+                    }
+                }
+                VisitSubtreeIfAction.SkipSubtree
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingNodes).isEqualTo(5)
+            Truth.assertThat(sameClassNodes).isEqualTo(2)
+            Truth.assertThat(sameKeyDifferentClassNodes).isEqualTo(3)
+            Truth.assertThat(otherNodes).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun traverseSubtreeWithKey_sameKeyFromCallingNode_skipSubtreeOfSameClass() {
+        var totalMatchingNodes = 0
+        // All these are in relation to the parent class where we run the traversal.
+        var sameClassNodes = 0
+        var sameKeyDifferentClassNodes = 0
+        var otherNodes = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeIfWithKey(SHARED_TRAVERSAL_NODE_KEY) {
+                totalMatchingNodes++
+
+                val action = when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        sameClassNodes++
+                        VisitSubtreeIfAction.SkipSubtree
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        sameKeyDifferentClassNodes++
+                        VisitSubtreeIfAction.VisitSubtree
+                    }
+
+                    else -> {
+                        otherNodes++
+                        VisitSubtreeIfAction.VisitSubtree
+                    }
+                }
+                action
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingNodes).isEqualTo(8)
+            Truth.assertThat(sameClassNodes).isEqualTo(4)
+            Truth.assertThat(sameKeyDifferentClassNodes).isEqualTo(4)
+            Truth.assertThat(otherNodes).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun traverseSubtreeWithKey_sameKeyFromCallingNode_cancelTraversalOfSameClass() {
+        var totalMatchingNodes = 0
+        // All these are in relation to the parent class where we run the traversal.
+        var sameClassNodes = 0
+        var sameKeyDifferentClassNodes = 0
+        var otherNodes = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeIfWithKey(SHARED_TRAVERSAL_NODE_KEY) {
+                totalMatchingNodes++
+
+                val action = when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        sameClassNodes++
+                        VisitSubtreeIfAction.CancelTraversal
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        sameKeyDifferentClassNodes++
+                        VisitSubtreeIfAction.VisitSubtree
+                    }
+
+                    else -> {
+                        otherNodes++
+                        VisitSubtreeIfAction.VisitSubtree
+                    }
+                }
+                action
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingNodes).isEqualTo(1)
+            Truth.assertThat(sameClassNodes).isEqualTo(1)
+            Truth.assertThat(sameKeyDifferentClassNodes).isEqualTo(0)
+            Truth.assertThat(otherNodes).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun traverseSubtreeWithKey_sameKeyFromCallingNode_skipSubtreeOfDifferentClassSameKey() {
+        var totalMatchingNodes = 0
+        // All these are in relation to the parent class where we run the traversal.
+        var sameClassNodes = 0
+        var sameKeyDifferentClassNodes = 0
+        var otherNodes = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeIfWithKey(SHARED_TRAVERSAL_NODE_KEY) {
+                totalMatchingNodes++
+
+                val action = when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        sameClassNodes++
+                        VisitSubtreeIfAction.VisitSubtree
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        sameKeyDifferentClassNodes++
+                        VisitSubtreeIfAction.SkipSubtree
+                    }
+
+                    else -> {
+                        otherNodes++
+                        VisitSubtreeIfAction.VisitSubtree
+                    }
+                }
+                action
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingNodes).isEqualTo(7)
+            Truth.assertThat(sameClassNodes).isEqualTo(3)
+            Truth.assertThat(sameKeyDifferentClassNodes).isEqualTo(4)
+            Truth.assertThat(otherNodes).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun traverseSubtreeWithKey_sameKeyFromCallingNode_cancelTraversalOfDifferentClassSameKey() {
+        var totalMatchingNodes = 0
+        // All these are in relation to the parent class where we run the traversal.
+        var sameClassNodes = 0
+        var sameKeyDifferentClassNodes = 0
+        var otherNodes = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeIfWithKey(SHARED_TRAVERSAL_NODE_KEY) {
+                totalMatchingNodes++
+
+                val action = when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        sameClassNodes++
+                        VisitSubtreeIfAction.VisitSubtree
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        sameKeyDifferentClassNodes++
+                        VisitSubtreeIfAction.CancelTraversal
+                    }
+
+                    else -> {
+                        otherNodes++
+                        VisitSubtreeIfAction.VisitSubtree
+                    }
+                }
+                action
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingNodes).isEqualTo(3)
+            Truth.assertThat(sameClassNodes).isEqualTo(2)
+            Truth.assertThat(sameKeyDifferentClassNodes).isEqualTo(1)
+            Truth.assertThat(otherNodes).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun traverseSubtreeWithKeyIf_differentKeyFromCallingNode_alwaysContinueTraversal() {
+        var totalMatchingNodes = 0
+        var classOneWithSharedKeyTraversalNodeAncestors = 0
+        var classTwoWithSharedKeyTraversalNodeAncestors = 0
+        // Only class with key = OTHER_TRAVERSAL_NODE_KEY.
+        var classThreeWithOtherKeyTraversalNodeAncestors = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeIfWithKey(OTHER_TRAVERSAL_NODE_KEY) {
+                totalMatchingNodes++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        classOneWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        classTwoWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        classThreeWithOtherKeyTraversalNodeAncestors++
+                    }
+                }
+                VisitSubtreeIfAction.VisitSubtree
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingNodes).isEqualTo(4)
+            // Should be zero because it won't match the shared key
+            Truth.assertThat(classOneWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            // Should be zero because it won't match the shared key
+            Truth.assertThat(classTwoWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            Truth.assertThat(classThreeWithOtherKeyTraversalNodeAncestors).isEqualTo(4)
+        }
+    }
+
+    @Test
+    fun traverseSubtreeWithKeyIf_differentKeyFromCallingNode_alwaysSkipSubtree() {
+        var totalMatchingNodes = 0
+        var classOneWithSharedKeyTraversalNodeAncestors = 0
+        var classTwoWithSharedKeyTraversalNodeAncestors = 0
+        // Only class with key = OTHER_TRAVERSAL_NODE_KEY.
+        var classThreeWithOtherKeyTraversalNodeAncestors = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeIfWithKey(OTHER_TRAVERSAL_NODE_KEY) {
+                totalMatchingNodes++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        classOneWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        classTwoWithSharedKeyTraversalNodeAncestors++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        classThreeWithOtherKeyTraversalNodeAncestors++
+                    }
+                }
+                VisitSubtreeIfAction.SkipSubtree
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingNodes).isEqualTo(3)
+            // Should be zero because it won't match the shared key
+            Truth.assertThat(classOneWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            // Should be zero because it won't match the shared key
+            Truth.assertThat(classTwoWithSharedKeyTraversalNodeAncestors).isEqualTo(0)
+            Truth.assertThat(classThreeWithOtherKeyTraversalNodeAncestors).isEqualTo(3)
+        }
+    }
+
+    // Matches only keys that are set to null (of which there are none).
+    @Test
+    fun traverseSubtreeWithKeyIf_nullKey_alwaysContinueTraversal() {
+        var totalMatchingNodes = 0
+        var sameClassNodes = 0
+        var sameKeyDifferentClassNodes = 0
+        var differentKeyDifferentClassNodes = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeIfWithKey(null) {
+                totalMatchingNodes++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        sameClassNodes++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        sameKeyDifferentClassNodes++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        differentKeyDifferentClassNodes++
+                    }
+                }
+                VisitSubtreeIfAction.VisitSubtree
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingNodes).isEqualTo(0)
+            Truth.assertThat(sameClassNodes).isEqualTo(0)
+            Truth.assertThat(sameKeyDifferentClassNodes).isEqualTo(0)
+            Truth.assertThat(differentKeyDifferentClassNodes).isEqualTo(0)
+        }
+    }
+
+    // Matches only keys that are set to null (of which there are none).
+    @Test
+    fun traverseSubtreeWithKeyIf_nullKey_alwaysSkipSubtree() {
+        var totalMatchingNodes = 0
+        // All these are in relation to the parent class where we run the traversal.
+        var sameClassNodes = 0
+        var sameKeyDifferentClassNodes = 0
+        var differentKeyDifferentClassNodes = 0
+
+        rule.runOnIdle {
+            parentNode.traverseSubtreeIfWithKey(null) {
+                totalMatchingNodes++
+
+                when (it) {
+                    is ClassOneWithSharedKeyTraversalNode -> {
+                        sameClassNodes++
+                    }
+
+                    is ClassTwoWithSharedKeyTraversalNode -> {
+                        sameKeyDifferentClassNodes++
+                    }
+
+                    is ClassThreeWithOtherKeyTraversalNode -> {
+                        differentKeyDifferentClassNodes++
+                    }
+                }
+                VisitSubtreeIfAction.SkipSubtree
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(totalMatchingNodes).isEqualTo(0)
+            Truth.assertThat(sameClassNodes).isEqualTo(0)
+            Truth.assertThat(sameKeyDifferentClassNodes).isEqualTo(0)
+            Truth.assertThat(differentKeyDifferentClassNodes).isEqualTo(0)
+        }
+    }
+}
+
+// Keys used across all test classes for testing [TraversalNode].
+private const val SHARED_TRAVERSAL_NODE_KEY = "SHARED_TRAVERSAL_NODE_KEY"
+private const val OTHER_TRAVERSAL_NODE_KEY = "OTHER_TRAVERSAL_NODE_KEY"
+
+// *********** Class One code (uses shared key in tests and contains funs for testing). ***********
+private fun Modifier.testTraversalNodeClassOneWithSharedKey(
+    label: String,
+    block: (ClassOneWithSharedKeyTraversalNode.() -> Unit)? = null
+) = this then TestTraversalModifierElementClassOneWithSharedKey(
+    label = label,
+    block = block
+)
+
+private data class TestTraversalModifierElementClassOneWithSharedKey(
+    val label: String,
+    val block: (ClassOneWithSharedKeyTraversalNode.() -> Unit)?
+) : ModifierNodeElement<ClassOneWithSharedKeyTraversalNode>() {
+    override fun create() =
+        ClassOneWithSharedKeyTraversalNode(label = label, block = block)
+
+    override fun update(node: ClassOneWithSharedKeyTraversalNode) {
+        node.label = label
+        node.block = block
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "testTraversalNodeClassOneWithSharedKey"
+        properties["label"] = label
+        properties["block"] = block
+    }
+}
+
+/*
+ * Main class for testing all the [TraversableNode] functions. The [block] parameter is for setting
+ * variable in the test to this instance so those publicly available [TraversableNode] functions
+ * can be called directly for testing.
+ *
+ * This isn't an example of how to use [TraversableNode]. Instead, you should call all the
+ * traversal methods from within your class when you need to do some operation on nodes of the same
+ * kind/key in the tree.
+ */
+private class ClassOneWithSharedKeyTraversalNode(
+    var label: String,
+    var block: (ClassOneWithSharedKeyTraversalNode.() -> Unit)?
+) : Modifier.Node(), TraversableNode {
+    override val traverseKey = SHARED_TRAVERSAL_NODE_KEY
+
+    init {
+        block?.let {
+            it()
+        }
+    }
+
+    override fun toString() =
+        "ClassOneWithSharedKeyTraversalNode($label) of $SHARED_TRAVERSAL_NODE_KEY"
+}
+
+// *********** Test Class Two code (uses shared key in tests, simple test class). ***********
+private fun Modifier.testTraversalNodeClassTwoWithSharedKey(
+    label: String,
+    block: (ClassTwoWithSharedKeyTraversalNode.() -> Unit)? = null
+) = this then TestTraversalModifierElementClassTwoWithSharedKey(
+    label = label,
+    block = block
+)
+
+private data class TestTraversalModifierElementClassTwoWithSharedKey(
+    val label: String,
+    val block: (ClassTwoWithSharedKeyTraversalNode.() -> Unit)?
+) : ModifierNodeElement<ClassTwoWithSharedKeyTraversalNode>() {
+    override fun create() = ClassTwoWithSharedKeyTraversalNode(
+        label = label,
+        block = block
+    )
+
+    override fun update(node: ClassTwoWithSharedKeyTraversalNode) {
+        node.label = label
+        node.block = block
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "testTraversalNodeClassTwoWithSharedKey"
+        properties["label"] = label
+        properties["block"] = block
+    }
+}
+
+private class ClassTwoWithSharedKeyTraversalNode(
+    var label: String,
+    var block: (ClassTwoWithSharedKeyTraversalNode.() -> Unit)?
+) :
+    Modifier.Node(), TraversableNode {
+
+    override val traverseKey = SHARED_TRAVERSAL_NODE_KEY
+
+    init {
+        block?.let {
+            it()
+        }
+    }
+
+    override fun toString() =
+        "ClassTwoWithSharedKeyTraversalNode($label) of $SHARED_TRAVERSAL_NODE_KEY"
+}
+
+// *********** Test Class Three code (uses other key in tests, simple test class). ***********
+private fun Modifier.testTraversalNodeClassThreeWithOtherKey(
+    label: String,
+    block: (ClassThreeWithOtherKeyTraversalNode.() -> Unit)? = null
+) = this then TestTraversalModifierElementClassThreeWithOtherKey(
+    label = label,
+    block
+)
+
+private data class TestTraversalModifierElementClassThreeWithOtherKey(
+    val label: String,
+    val block: (ClassThreeWithOtherKeyTraversalNode.() -> Unit)?
+) : ModifierNodeElement<ClassThreeWithOtherKeyTraversalNode>() {
+
+    override fun create() =
+        ClassThreeWithOtherKeyTraversalNode(label = label, block = block)
+
+    override fun update(node: ClassThreeWithOtherKeyTraversalNode) {
+        node.label = label
+        node.block = block
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "testTraversalNodeOtherKey"
+        properties["label"] = label
+        properties["block"] = block
+    }
+}
+
+private class ClassThreeWithOtherKeyTraversalNode(
+    var label: String,
+    var block: (ClassThreeWithOtherKeyTraversalNode.() -> Unit)?
+) : Modifier.Node(), TraversableNode {
+    override val traverseKey = OTHER_TRAVERSAL_NODE_KEY
+
+    init {
+        block?.let {
+            it()
+        }
+    }
+
+    override fun toString() =
+        "ClassThreeWithOtherKeyTraversalNode($label) of $OTHER_TRAVERSAL_NODE_KEY"
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
index ffe8b60..3225799 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
@@ -237,6 +237,9 @@
                 }
                 val info: AccessibilityNodeInfo = AccessibilityNodeInfo()
                 delegate.onInitializeAccessibilityNodeInfo(view, info)
+                if (info.isVisibleToUser()) {
+                    throw exception
+                }
                 if (!info.isScreenReaderFocusable()) {
                     throw exception
                 }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt
index e0846ac..cf0b7e1 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/MotionEventAdapter.android.kt
@@ -262,6 +262,7 @@
         val pressure = motionEvent.getPressure(index)
 
         var position = Offset(motionEvent.getX(index), motionEvent.getY(index))
+        val originalPositionEventPosition = position.copy()
         val rawPosition: Offset
         if (index == 0) {
             rawPosition = Offset(motionEvent.rawX, motionEvent.rawY)
@@ -287,9 +288,11 @@
                 val x = getHistoricalX(index, pos)
                 val y = getHistoricalY(index, pos)
                 if (x.isFinite() && y.isFinite()) {
+                    val originalEventPosition = Offset(x, y) // hit path will convert to local
                     val historicalChange = HistoricalChange(
                         getHistoricalEventTime(pos),
-                        Offset(x, y)
+                        originalEventPosition,
+                        originalEventPosition
                     )
                     historical.add(historicalChange)
                 }
@@ -330,7 +333,8 @@
             toolType,
             issuesEnterExit,
             historical,
-            scrollDelta
+            scrollDelta,
+            originalPositionEventPosition,
         )
     }
 }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index ef354a4..5859ede 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -60,6 +60,7 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.referentialEqualityPolicy
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.SessionMutex
@@ -101,6 +102,8 @@
 import androidx.compose.ui.input.key.Key.Companion.Enter
 import androidx.compose.ui.input.key.Key.Companion.Escape
 import androidx.compose.ui.input.key.Key.Companion.NumPadEnter
+import androidx.compose.ui.input.key.Key.Companion.PageDown
+import androidx.compose.ui.input.key.Key.Companion.PageUp
 import androidx.compose.ui.input.key.Key.Companion.Tab
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
@@ -815,6 +818,10 @@
                     info: AccessibilityNodeInfoCompat
                 ) {
                     super.onInitializeAccessibilityNodeInfo(host, info)
+
+                    // Prevent TalkBack from trying to focus the AndroidViewHolder
+                    info.setVisibleToUser(false)
+
                     var parentId = layoutNode
                         .findClosestParentNode { it.nodes.has(Nodes.Semantics) }
                         ?.semanticsId
@@ -1171,8 +1178,12 @@
             Tab -> if (keyEvent.isShiftPressed) Previous else Next
             DirectionRight -> Right
             DirectionLeft -> Left
-            DirectionUp -> Up
-            DirectionDown -> Down
+            // For the initial key input of a new composable, both up/down and page up/down will
+            // trigger the composable to get focus (so the composable can handle key events to
+            // move focus or scroll content). Remember, composables can't receive key events without
+            // focus.
+            DirectionUp, PageUp -> Up
+            DirectionDown, PageDown -> Down
             DirectionCenter, Enter, NumPadEnter -> FocusDirection.Enter
             Back, Escape -> Exit
             else -> null
@@ -1184,6 +1195,7 @@
             invalidateLayers(root)
         }
         measureAndLayout()
+        Snapshot.sendApplyNotifications()
 
         isDrawingContent = true
         // we don't have to observe here because the root has a layer modifier
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index 047c75e..ee90886 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -50,6 +50,9 @@
 import androidx.collection.ArrayMap
 import androidx.collection.ArraySet
 import androidx.collection.SparseArrayCompat
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.R
 import androidx.compose.ui.geometry.Offset
@@ -262,13 +265,14 @@
 
     /**
      * True if any content capture service enabled in the system.
-     *
-     * TODO(b/272068594): follow up on improving the performance and actually enabling the content
-     * capture feature in production later.
      */
+    @OptIn(ExperimentalComposeUiApi::class)
     private val isEnabledForContentCapture: Boolean
         get() {
-            return contentCaptureForceEnabledForTesting
+            if (DisableContentCapture) {
+                return false
+            }
+            return contentCaptureSession != null || contentCaptureForceEnabledForTesting
         }
 
     /**
@@ -3642,3 +3646,16 @@
  */
 internal fun AndroidViewsHandler.semanticsIdToView(id: Int): View? =
     layoutNodeToHolder.entries.firstOrNull { it.key.semanticsId == id }?.value
+
+/**
+ * A flag to force disable the content capture feature.
+ *
+ * If you find any issues with the new feature, flip this flag to true to confirm they are newly
+ * introduced then file a bug.
+ */
+@Suppress("GetterSetterNames", "OPT_IN_MARKER_ON_WRONG_TARGET")
+@get:Suppress("GetterSetterNames")
+@get:ExperimentalComposeUiApi
+@set:ExperimentalComposeUiApi
+@ExperimentalComposeUiApi
+var DisableContentCapture: Boolean by mutableStateOf(false)
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
index bef25e7e..12dd53e 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
@@ -202,6 +202,10 @@
     override val isValidOwnerScope: Boolean
         get() = isAttachedToWindow
 
+    override fun getAccessibilityClassName(): CharSequence {
+        return javaClass.name
+    }
+
     override fun onReuse() {
         // We reset at the same time we remove the view. So if the view was removed, we can just
         // re-add it and it's ready to go. If it's already attached, we didn't reset it and need
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRestorer.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRestorer.kt
index c490f79..bfa2812 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRestorer.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRestorer.kt
@@ -19,7 +19,6 @@
 import androidx.compose.runtime.saveable.LocalSaveableStateRegistry
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRestorerNode.Companion.FocusRestorerElement
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.Nodes
 import androidx.compose.ui.node.currentValueOf
@@ -63,17 +62,25 @@
 
 // TODO: Move focusRestorer to foundation after saveFocusedChild and restoreFocusedChild are stable.
 /**
- * This modifier can be uses to save and restore focus to a focus group.
+ * This modifier can be used to save and restore focus to a focus group.
  * When focus leaves the focus group, it stores a reference to the item that was previously focused.
  * Then when focus re-enters this focus group, it restores focus to the previously focused item.
  *
+ * @param onRestoreFailed callback provides a lambda that is invoked if focus restoration fails.
+ * This lambda can be used to return a custom fallback item by providing a [FocusRequester]
+ * attached to that item. This can be used to customize the initially focused item.
+ *
  * @sample androidx.compose.ui.samples.FocusRestorerSample
+ * @sample androidx.compose.ui.samples.FocusRestorerCustomFallbackSample
  */
 @ExperimentalComposeUiApi
-fun Modifier.focusRestorer(): Modifier = this then FocusRestorerElement
+fun Modifier.focusRestorer(
+    onRestoreFailed: (() -> FocusRequester)? = null
+): Modifier = this then FocusRestorerElement(onRestoreFailed)
 
-internal class FocusRestorerNode :
-    FocusPropertiesModifierNode, FocusRequesterModifierNode, Modifier.Node() {
+internal class FocusRestorerNode(
+    var onRestoreFailed: (() -> FocusRequester)?
+) : FocusPropertiesModifierNode, FocusRequesterModifierNode, Modifier.Node() {
     private val onExit: (FocusDirection) -> FocusRequester = {
         @OptIn(ExperimentalComposeUiApi::class)
         saveFocusedChild()
@@ -83,22 +90,32 @@
     @OptIn(ExperimentalComposeUiApi::class)
     private val onEnter: (FocusDirection) -> FocusRequester = {
         @OptIn(ExperimentalComposeUiApi::class)
-        if (restoreFocusedChild()) FocusRequester.Cancel else FocusRequester.Default
+        if (restoreFocusedChild()) {
+            FocusRequester.Cancel
+        } else {
+            onRestoreFailed?.invoke() ?: FocusRequester.Default
+        }
     }
+
     override fun applyFocusProperties(focusProperties: FocusProperties) {
         @OptIn(ExperimentalComposeUiApi::class)
         focusProperties.enter = onEnter
         @OptIn(ExperimentalComposeUiApi::class)
         focusProperties.exit = onExit
     }
+}
 
-    companion object {
-        val FocusRestorerElement = object : ModifierNodeElement<FocusRestorerNode>() {
-            override fun create() = FocusRestorerNode()
-            override fun update(node: FocusRestorerNode) {}
-            override fun InspectorInfo.inspectableProperties() { name = "focusRestorer" }
-            override fun hashCode(): Int = "focusRestorer".hashCode()
-            override fun equals(other: Any?) = other === this
-        }
+private data class FocusRestorerElement(
+    val onRestoreFailed: (() -> FocusRequester)?
+) : ModifierNodeElement<FocusRestorerNode>() {
+    override fun create() = FocusRestorerNode(onRestoreFailed)
+
+    override fun update(node: FocusRestorerNode) {
+        node.onRestoreFailed = onRestoreFailed
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "focusRestorer"
+        properties["onRestoreFailed"] = onRestoreFailed
     }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
index 1e6d25b..1b0994a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
@@ -387,7 +387,8 @@
                     historical.add(
                         HistoricalChange(
                             it.uptimeMillis,
-                            coordinates!!.localPositionOf(parentCoordinates, it.position)
+                            coordinates!!.localPositionOf(parentCoordinates, it.position),
+                            it.originalEventPosition
                         )
                     )
                 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerInput.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerInput.kt
index ed60c9a..a7a1a96 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerInput.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerInput.kt
@@ -50,7 +50,8 @@
     val type: PointerType,
     val issuesEnterExit: Boolean = false,
     val historical: List<HistoricalChange> = mutableListOf(),
-    val scrollDelta: Offset = Offset.Zero
+    val scrollDelta: Offset = Offset.Zero,
+    val originalEventPosition: Offset = Offset.Zero,
 )
 
 /**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
index 7a6010a..35aedef 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
@@ -499,6 +499,7 @@
         type: PointerType,
         historical: List<HistoricalChange>,
         scrollDelta: Offset,
+        originalEventPosition: Offset,
     ) : this(
         id,
         uptimeMillis,
@@ -513,6 +514,7 @@
         scrollDelta
     ) {
         _historical = historical
+        this.originalEventPosition = originalEventPosition
     }
 
     /**
@@ -527,9 +529,12 @@
     @get:ExperimentalComposeUiApi
     val historical: List<HistoricalChange>
         get() = _historical ?: listOf()
+
     @OptIn(ExperimentalComposeUiApi::class)
     private var _historical: List<HistoricalChange>? = null
 
+    internal var originalEventPosition: Offset = Offset.Zero
+
     /**
      * Indicates whether the change was consumed or not. Note that the change must be consumed in
      * full as there's no partial consumption system provided.
@@ -590,7 +595,8 @@
         consumed.downChange || consumed.positionChange,
         type,
         this.historical,
-        this.scrollDelta
+        this.scrollDelta,
+        this.originalEventPosition,
     ).also {
         this.consumed = consumed
     }
@@ -663,7 +669,8 @@
         consumed.downChange || consumed.positionChange,
         type,
         this.historical,
-        scrollDelta
+        scrollDelta,
+        this.originalEventPosition,
     ).also {
         this.consumed = consumed
     }
@@ -701,7 +708,8 @@
         isInitiallyConsumed = false, // doesn't matter, we will pass a holder anyway
         type,
         historical = this.historical,
-        scrollDelta
+        scrollDelta,
+        this.originalEventPosition,
     ).also {
         it.consumed = this.consumed
     }
@@ -755,6 +763,7 @@
         id: PointerId = this.id,
         currentTime: Long = this.uptimeMillis,
         currentPosition: Offset = this.position,
+        originalEventPosition: Offset = this.originalEventPosition,
         currentPressed: Boolean = this.pressed,
         pressure: Float = this.pressure,
         previousTime: Long = this.previousUptimeMillis,
@@ -763,7 +772,7 @@
         type: PointerType = this.type,
         historical: List<HistoricalChange> = this.historical,
         scrollDelta: Offset = this.scrollDelta
-        ): PointerInputChange = PointerInputChange(
+    ): PointerInputChange = PointerInputChange(
         id,
         currentTime,
         currentPosition,
@@ -775,7 +784,8 @@
         isInitiallyConsumed = false, // doesn't matter, we will pass a holder anyway
         type,
         historical,
-        scrollDelta
+        scrollDelta,
+        originalEventPosition,
     ).also {
         it.consumed = this.consumed
     }
@@ -814,6 +824,17 @@
     val uptimeMillis: Long,
     val position: Offset
 ) {
+    internal var originalEventPosition: Offset = Offset.Zero
+        private set
+
+    internal constructor(
+        uptimeMillis: Long,
+        position: Offset,
+        originalEventPosition: Offset
+    ) : this(uptimeMillis, position) {
+        this.originalEventPosition = originalEventPosition
+    }
+
     override fun toString(): String {
         return "HistoricalChange(uptimeMillis=$uptimeMillis, " +
             "position=$position)"
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerIcon.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerIcon.kt
index a1ac20b..f55c989 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerIcon.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerIcon.kt
@@ -16,22 +16,21 @@
 
 package androidx.compose.ui.input.pointer
 
-import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
 import androidx.compose.ui.input.pointer.PointerEventPass.Main
-import androidx.compose.ui.modifier.ModifierLocalConsumer
-import androidx.compose.ui.modifier.ModifierLocalProvider
-import androidx.compose.ui.modifier.ModifierLocalReadScope
-import androidx.compose.ui.modifier.ProvidableModifierLocal
-import androidx.compose.ui.modifier.modifierLocalOf
+import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.PointerInputModifierNode
+import androidx.compose.ui.node.TraversableNode
+import androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction
+import androidx.compose.ui.node.currentValueOf
+import androidx.compose.ui.node.traverseAncestors
+import androidx.compose.ui.node.traverseSubtree
+import androidx.compose.ui.node.traverseSubtreeIf
+import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.LocalPointerIconService
-import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.IntSize
 
 /**
  * Represents a pointer icon to use in [Modifier.pointerHoverIcon]
@@ -71,172 +70,251 @@
 
 /**
  * Modifier that lets a developer define a pointer icon to display when the cursor is hovered over
- * the element. When [overrideDescendants] is set to true, children cannot override the pointer icon
- * using this modifier.
+ * the element. When [overrideDescendants] is set to true, descendants cannot override the
+ * pointer icon using this modifier.
  *
  * @sample androidx.compose.ui.samples.PointerIconSample
  *
  * @param icon The icon to set
  * @param overrideDescendants when false (by default) descendants are able to set their own pointer
- * icon. If true, all children under this parent will receive the requested pointer [icon] and are
- * no longer allowed to override their own pointer icon.
+ * icon. If true, no descendants under this parent are eligible to change the icon (it will be set
+ * to the this [the parent's] icon).
  */
 @Stable
 fun Modifier.pointerHoverIcon(icon: PointerIcon, overrideDescendants: Boolean = false) =
-    composed(inspectorInfo = debugInspectorInfo {
+    this then PointerHoverIconModifierElement(
+        icon = icon,
+        overrideDescendants = overrideDescendants
+    )
+
+internal data class PointerHoverIconModifierElement(
+    val icon: PointerIcon,
+    val overrideDescendants: Boolean = false
+) : ModifierNodeElement<PointerHoverIconModifierNode>() {
+    override fun create() = PointerHoverIconModifierNode(icon, overrideDescendants)
+
+    override fun update(node: PointerHoverIconModifierNode) {
+        node.icon = icon
+        node.overrideDescendants = overrideDescendants
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
         name = "pointerHoverIcon"
         properties["icon"] = icon
         properties["overrideDescendants"] = overrideDescendants
-    }) {
-        val pointerIconService = LocalPointerIconService.current
-        if (pointerIconService == null) {
-            Modifier
-        } else {
-            val onSetIcon = { pointerIcon: PointerIcon? ->
-                pointerIconService.setIcon(pointerIcon)
-            }
-            val pointerIconModifierLocal = remember {
-                PointerIconModifierLocal(icon, overrideDescendants, onSetIcon)
-            }
-            SideEffect {
-                pointerIconModifierLocal.updateValues(
-                    icon = icon,
-                    overrideDescendants = overrideDescendants,
-                    onSetIcon = onSetIcon
-                )
-            }
-            val pointerInputModifier = if (pointerIconModifierLocal.shouldUpdatePointerIcon()) {
-                pointerInput(pointerIconModifierLocal) {
-                    awaitPointerEventScope {
-                        while (true) {
-                            val event = awaitPointerEvent(Main)
-
-                            if (event.type == PointerEventType.Enter) {
-                                pointerIconModifierLocal.enter()
-                            } else if (event.type == PointerEventType.Exit) {
-                                pointerIconModifierLocal.exit()
-                            }
-                        }
-                    }
-                }
-            } else {
-                Modifier
-            }
-
-            pointerIconModifierLocal.then(pointerInputModifier)
-        }
-    }
-
-/**
- * Handles storing all pointer icon information that needs to be passed between Modifiers to
- * determine which icon needs to be set in the hierarchy.
- *
- * @property icon the stored current icon we are keeping track of.
- * @property overrideDescendants value indicating whether the stored icon should always be
- * respected by its children. If true, the stored icon will be considered the source of truth for
- * all children. If false, the stored icon can be overwritten by a child.
- * @property onSetIcon is a lambda that will handle the process of physically setting the user
- * facing pointer icon. This allows the [PointerIconModifierLocal] to be solely responsible for
- * determining what the state of the icon should be, but removes the responsibility of needing to
- * actually set the icon for the user.
- */
-private class PointerIconModifierLocal(
-    private var icon: PointerIcon,
-    private var overrideDescendants: Boolean,
-    private var onSetIcon: (PointerIcon?) -> Unit,
-) : PointerIcon, ModifierLocalProvider<PointerIconModifierLocal?>, ModifierLocalConsumer {
-    // TODO: (b/266976920) Remove making this a mutable state once we fully support a dynamic
-    //  overrideDescendants param.
-    private var parentInfo: PointerIconModifierLocal? by mutableStateOf(null)
-
-    // TODO: (b/267170292) Properly reset isPaused upon PointerIconModifierLocal disposal.
-    var isPaused: Boolean = false
-
-    /* True if the cursor is within the surface area of this element's bounds. Otherwise, false. */
-    var isHovered: Boolean = false
-
-    override val key: ProvidableModifierLocal<PointerIconModifierLocal?> = ModifierLocalPointerIcon
-    override val value: PointerIconModifierLocal = this
-
-    override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) = with(scope) {
-        val oldParentInfo = parentInfo
-        parentInfo = ModifierLocalPointerIcon.current
-        if (oldParentInfo != null && parentInfo == null) {
-            // When the old parentInfo for this element is reassigned to null, we assume this
-            // element is being alienated for disposal. Exit out of our pointer icon logic for this
-            // element and then update onSetIcon to null so it will not change the icon any further.
-            exit(oldParentInfo)
-            onSetIcon = {}
-        }
-    }
-
-    fun shouldUpdatePointerIcon(): Boolean {
-        val parentPointerInfo = parentInfo
-        return parentPointerInfo == null || !parentPointerInfo.hasOverride()
-    }
-
-    private fun hasOverride(): Boolean {
-        return overrideDescendants || parentInfo?.hasOverride() == true
-    }
-
-    fun enter() {
-        isHovered = true
-        if (!isPaused) {
-            parentInfo?.pause()
-            onSetIcon(icon)
-        }
-    }
-
-    fun exit() {
-        exit(parentInfo)
-    }
-
-    private fun exit(parent: PointerIconModifierLocal?) {
-        if (isHovered) {
-            if (parent == null) {
-                // Notify that oldest ancestor in hierarchy exited by passing null to onSetIcon().
-                onSetIcon(null)
-            } else {
-                parent.reassignIcon()
-            }
-        }
-        isHovered = false
-    }
-
-    private fun reassignIcon() {
-        isPaused = false
-        if (isHovered) {
-            onSetIcon(icon)
-        } else if (parentInfo == null) {
-            // Reassign the icon back to the default arrow by passing in a null PointerIcon
-            onSetIcon(null)
-        } else {
-            parentInfo?.reassignIcon()
-        }
-    }
-
-    private fun pause() {
-        isPaused = true
-        parentInfo?.pause()
-    }
-
-    fun updateValues(
-        icon: PointerIcon,
-        overrideDescendants: Boolean,
-        onSetIcon: (PointerIcon?) -> Unit
-    ) {
-        if (this.icon != icon && isHovered && !isPaused) {
-            // Hovered element's icon has dynamically changed so we need to set the user facing icon
-            onSetIcon(icon)
-        }
-        this.icon = icon
-        this.overrideDescendants = overrideDescendants
-        this.onSetIcon = onSetIcon
     }
 }
 
-/**
- * The unique identifier used as the key for the custom [ModifierLocalProvider] created to tell us
- * the current [PointerIcon].
+/*
+ * Changes the pointer hover icon if the node is in bounds and if the node is not overridden
+ * by a parent pointer hover icon node. This node implements [PointerInputModifierNode] so it can
+ * listen to pointer input events and determine if the pointer has entered or exited the bounds of
+ * the modifier itself.
+ *
+ * If the icon or overrideDescendants values are changed, this node will determine if it needs to
+ * walk down and/or up the modifier chain to update those pointer hover icon modifier nodes as well.
  */
-private val ModifierLocalPointerIcon = modifierLocalOf<PointerIconModifierLocal?> { null }
+internal class PointerHoverIconModifierNode(
+    icon: PointerIcon,
+    overrideDescendants: Boolean = false
+) : Modifier.Node(),
+    TraversableNode,
+    PointerInputModifierNode,
+    CompositionLocalConsumerModifierNode {
+    /* Traversal key used with the [TraversableNode] interface to enable all the traversing
+     * functions (ancestor, child, subtree, and subtreeIf).
+     */
+    override val traverseKey = "androidx.compose.ui.input.pointer.PointerHoverIcon"
+
+    var icon = icon
+        set(value) {
+            if (field != value) {
+                field = value
+                if (cursorInBoundsOfNode) {
+                    displayIconIfDescendantsDoNotHavePriority()
+                }
+            }
+        }
+
+    var overrideDescendants = overrideDescendants
+        set(value) {
+            if (field != value) {
+                field = value
+
+                if (overrideDescendants) { // overrideDescendants changed from false -> true
+                    // If this node or any descendants have the cursor in bounds, change the icon.
+                    if (cursorInBoundsOfNode) {
+                        displayIcon()
+                    }
+                } else { // overrideDescendants changed from true -> false
+                    if (cursorInBoundsOfNode) {
+                        displayIconFromCurrentNodeOrDescendantsWithCursorInBounds()
+                    }
+                }
+            }
+        }
+
+    // Service used to actually update the icon with the system when needed.
+    private val pointerIconService: PointerIconService?
+        get() = currentValueOf(LocalPointerIconService)
+
+    private var cursorInBoundsOfNode = false
+
+    // Pointer Input callback for determining if a Pointer has Entered or Exited this node.
+    override fun onPointerEvent(
+        pointerEvent: PointerEvent,
+        pass: PointerEventPass,
+        bounds: IntSize
+    ) {
+        if (pass == Main) {
+            // Cursor within the surface area of this node's bounds
+            if (pointerEvent.type == PointerEventType.Enter) {
+                cursorInBoundsOfNode = true
+                displayIconIfDescendantsDoNotHavePriority()
+            } else if (pointerEvent.type == PointerEventType.Exit) {
+                cursorInBoundsOfNode = false
+                displayIconFromAncestorNodeWithCursorInBoundsOrDefaultIcon()
+            }
+        }
+    }
+
+    override fun onCancelPointerInput() {
+        // We aren't processing the event (only listening for enter/exit), so we don't need to
+        // do anything.
+    }
+
+    override fun onDetach() {
+        cursorInBoundsOfNode = false
+        displayIconFromAncestorNodeWithCursorInBoundsOrDefaultIcon()
+
+        super.onDetach()
+    }
+
+    private fun displayIcon() {
+        // If there are any ancestor that override this node, we must use that icon. Otherwise, we
+        // use the current node's icon
+        val iconToUse = findOverridingAncestorNode()?.icon ?: icon
+        pointerIconService?.setIcon(iconToUse)
+    }
+
+    private fun displayDefaultIcon() {
+        pointerIconService?.setIcon(null)
+    }
+
+    private fun displayIconIfDescendantsDoNotHavePriority() {
+        var hasIconRightsOverDescendants = true
+
+        if (!overrideDescendants) {
+            traverseSubtree {
+                // Descendant in bounds has rights to the icon (and has already set it),
+                // so we ignore.
+                val continueTraversal = if (it.cursorInBoundsOfNode) {
+                    hasIconRightsOverDescendants = false
+                    false
+                } else {
+                    true
+                }
+                continueTraversal
+            }
+        }
+
+        if (hasIconRightsOverDescendants) {
+            displayIcon()
+        }
+    }
+
+    /*
+     * Finds and returns the lowest descendant node with the cursor within its bounds (true node
+     * that gets to decide the icon).
+     *
+     * Note: Multiple descendant nodes may have `cursorInBoundsOfNode` set to true (for when the
+     * cursor enters their bounds). The lowest one is the one that is the correct node for the
+     * mouse (see example for explanation).
+     *
+     * Example: Parent node contains a child node within its visual border (both are pointer icon
+     * nodes).
+     * - Mouse moves over the PARENT node triggers the pointer input handler ENTER event which sets
+     * `cursorInBoundsOfNode` = `true`.
+     * - Mouse moves over CHILD node triggers the pointer input handler ENTER event which sets
+     * `cursorInBoundsOfNode` = `true`.
+     *
+     * They are both true now because the pointer input event's exit is not triggered (which would
+     * set cursorInBoundsOfNode` = `false`) unless the mouse moves outside the parent node. Because
+     * the child node is contained visually within the parent node, it is not triggered. That is why
+     * we need to get the lowest node with `cursorInBoundsOfNode` set to true.
+     */
+    private fun findDescendantNodeWithCursorInBounds(): PointerHoverIconModifierNode? {
+        var descendantNodeWithCursorInBounds: PointerHoverIconModifierNode? = null
+
+        traverseSubtreeIf {
+            var actionForSubtreeOfCurrentNode = VisitSubtreeIfAction.VisitSubtree
+
+            if (it.cursorInBoundsOfNode) {
+                descendantNodeWithCursorInBounds = it
+
+                // No descendant nodes below this one are eligible to set the icon.
+                if (it.overrideDescendants) {
+                    actionForSubtreeOfCurrentNode = VisitSubtreeIfAction.SkipSubtree
+                }
+            }
+            actionForSubtreeOfCurrentNode
+        }
+
+        return descendantNodeWithCursorInBounds
+    }
+
+    private fun displayIconFromCurrentNodeOrDescendantsWithCursorInBounds() {
+        if (!cursorInBoundsOfNode) return
+
+        var pointerHoverIconModifierNode: PointerHoverIconModifierNode = this
+
+        if (!overrideDescendants) {
+            findDescendantNodeWithCursorInBounds()?.let {
+                pointerHoverIconModifierNode = it
+            }
+        }
+
+        pointerHoverIconModifierNode.displayIcon()
+    }
+
+    private fun findOverridingAncestorNode(): PointerHoverIconModifierNode? {
+        var pointerHoverIconModifierNode: PointerHoverIconModifierNode? = null
+
+        traverseAncestors {
+            if (it.overrideDescendants &&
+                it.cursorInBoundsOfNode) {
+                pointerHoverIconModifierNode = it
+            }
+            // continue traversal
+            true
+        }
+
+        return pointerHoverIconModifierNode
+    }
+
+    /*
+     * Sets the icon to either the ancestor where the mouse is in its bounds (or to its
+     * ancestors if one overrides it) or to a default icon.
+     */
+    private fun displayIconFromAncestorNodeWithCursorInBoundsOrDefaultIcon() {
+        var pointerHoverIconModifierNode: PointerHoverIconModifierNode? = null
+
+        traverseAncestors {
+            if (pointerHoverIconModifierNode == null && it.cursorInBoundsOfNode) {
+                pointerHoverIconModifierNode = it
+
+            // We should only assign a node that override its descendants if there was a node
+            // below it where the mouse was in bounds meaning the pointerHoverIconModifierNode
+            // will not be null.
+            } else if (pointerHoverIconModifierNode != null &&
+                it.overrideDescendants &&
+                it.cursorInBoundsOfNode) {
+                pointerHoverIconModifierNode = it
+            }
+
+            // continue traversal
+            true
+        }
+        pointerHoverIconModifierNode?.displayIcon() ?: displayDefaultIcon()
+    }
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
index 052066b..d578e14 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
@@ -196,7 +196,8 @@
                     false,
                     it.type,
                     it.historical,
-                    it.scrollDelta
+                    it.scrollDelta,
+                    it.originalEventPosition
                 )
             )
             if (it.down) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
index 6a35182..4517319 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
@@ -109,6 +109,7 @@
     fun resetTracking() {
         xVelocityTracker.resetTracking()
         yVelocityTracker.resetTracking()
+        lastMoveEventTimeStamp = 0L
     }
 }
 
@@ -405,38 +406,22 @@
 }
 
 private fun VelocityTracker.addPointerInputChangeWithFix(event: PointerInputChange) {
-    // If this is ACTION_DOWN: Register down event as the starting point for the accumulator
-    // Since compose uses relative positions, for a more accurate velocity calculation we'll need
-    // to transform all events positions. We use the start of the movement signaled by the DOWN
-    // event as the start point. Any subsequent event will be accumulated into
-    // [currentPointerPositionAccumulator] and used to update the tracker.
-    // We also use this to reset [lastMoveEventTimeStamp].
+    // If this is ACTION_DOWN: Reset the tracking.
     if (event.changedToDownIgnoreConsumed()) {
-        lastMoveEventTimeStamp = 0L
-        currentPointerPositionAccumulator = event.position
         resetTracking()
-        return
     }
 
-    // If this is a ACTION_MOVE event: Add events to the tracker as per the platform implementation.
-    // ACTION_MOVE may or may not have a historical array. If they do have a historical array, use
-    // the data provided by the array only, if they do not have historical data, use the data
-    // provided by the event itself. This is in line with the platform implementation.
+    // If this is not ACTION_UP event: Add events to the tracker as per the platform implementation.
+    // In the platform implementation the historical events array is used, they store the current
+    // event data in the position HistoricalArray.Size. Our historical array doesn't have access
+    // to the final position, but we can get that information from the original event data X and Y
+    // coordinates.
     @OptIn(ExperimentalComposeUiApi::class)
-    if (!event.changedToUpIgnoreConsumed() && !event.changedToDownIgnoreConsumed()) {
-        lastMoveEventTimeStamp = event.uptimeMillis
-        if (event.historical.isEmpty()) {
-            val delta = event.position - currentPointerPositionAccumulator
-            currentPointerPositionAccumulator += delta
-            addPosition(event.uptimeMillis, currentPointerPositionAccumulator)
-        } else {
-            event.historical.fastForEach {
-                val historicalDelta = it.position - currentPointerPositionAccumulator
-                // Update the current position with the historical delta and add it to the tracker
-                currentPointerPositionAccumulator += historicalDelta
-                addPosition(it.uptimeMillis, currentPointerPositionAccumulator)
-            }
+    if (!event.changedToUpIgnoreConsumed()) {
+        event.historical.fastForEach {
+            addPosition(it.uptimeMillis, it.originalEventPosition)
         }
+        addPosition(event.uptimeMillis, event.originalEventPosition)
     }
 
     // If this is ACTION_UP. Fix for b/238654963. If there's been enough time after the last MOVE
@@ -444,6 +429,7 @@
     if (event.changedToUpIgnoreConsumed() && (event.uptimeMillis - lastMoveEventTimeStamp) > 40L) {
         resetTracking()
     }
+    lastMoveEventTimeStamp = event.uptimeMillis
 }
 
 internal data class DataPointAtTime(var time: Long, var dataPoint: Float)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt
index afe3564..4abf36f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt
@@ -102,6 +102,8 @@
     @JvmStatic
     inline val SoftKeyboardKeyInput
         get() = NodeKind<SoftKeyboardInterceptionModifierNode>(0b1 shl 17)
+    @JvmStatic
+    inline val Traversable get() = NodeKind<TraversableNode>(0b1 shl 18)
     // ...
 }
 
@@ -203,6 +205,9 @@
     if (node is SoftKeyboardInterceptionModifierNode) {
         mask = mask or Nodes.SoftKeyboardKeyInput
     }
+    if (node is TraversableNode) {
+        mask = mask or Nodes.Traversable
+    }
     return mask
 }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/TraversableNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/TraversableNode.kt
new file mode 100644
index 0000000..778e634
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/TraversableNode.kt
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.compose.ui.node
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.areObjectsOfSameType
+import androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction
+
+/**
+ * Allows [Modifier.Node] classes to traverse up/down the Node tree for classes of the same type or
+ * for a particular key (traverseKey).
+ *
+ * Note: The actual traversals are done in extension functions (see bottom of file).
+ */
+interface TraversableNode : DelegatableNode {
+    val traverseKey: Any
+
+    companion object {
+        /**
+         * Tree traversal actions for the traverseSubtreeWithKeyIf related functions:
+         *  - VisitSubtree - visit the subtree of current match
+         *  - SkipSubtree - do NOT visit the subtree of the current match
+         *  - CancelTraversal - cancels the traversal (returns from function call)
+         *
+         * To see examples of all the actions, see TraversableModifierNodeTest. For a return/cancel
+         * example specifically, see
+         * traverseSubtreeWithSameKeyIf_cancelTraversalOfDifferentClassSameKey().
+         */
+        enum class VisitSubtreeIfAction {
+            VisitSubtree,
+            SkipSubtree,
+            CancelTraversal
+        }
+    }
+}
+
+// *********** Nearest Traversable Ancestor methods ***********
+/**
+ * Finds the nearest ancestor with a matching [key].
+ */
+fun DelegatableNode.nearestTraversableAncestorWithKey(
+    key: Any?
+): TraversableNode? {
+    visitAncestors(Nodes.Traversable) {
+        if (key == it.traverseKey) {
+            return it
+        }
+    }
+    return null
+}
+
+/**
+ * Finds the nearest ancestor of the same class and key.
+ */
+fun <T> T.nearestTraversableAncestor(): T? where T : TraversableNode {
+    visitAncestors(Nodes.Traversable) {
+        if (this.traverseKey == it.traverseKey && areObjectsOfSameType(this, it)) {
+            @Suppress("UNCHECKED_CAST")
+            return it as T
+        }
+    }
+    return null
+}
+
+// *********** Traverse Ancestors methods ***********
+/**
+ * Executes [block] for all ancestors with a matching [key].
+ *
+ * Note: The parameter [block]'s return boolean value will determine if the traversal will
+ * continue (true = continue, false = cancel).
+ */
+fun DelegatableNode.traverseAncestorsWithKey(
+    key: Any?,
+    block: (TraversableNode) -> Boolean
+) {
+    visitAncestors(Nodes.Traversable) {
+        val continueTraversal = if (key == it.traverseKey) {
+            block(it)
+        } else {
+            true
+        }
+        if (!continueTraversal) return
+    }
+}
+
+/**
+ * Executes [block] for all ancestors of the same class and key.
+ *
+ * Note: The parameter [block]'s return boolean value will determine if the traversal will
+ * continue (true = continue, false = cancel).
+ */
+fun <T> T.traverseAncestors(block: (T) -> Boolean) where T : TraversableNode {
+    visitAncestors(Nodes.Traversable) {
+        val continueTraversal =
+            if (this.traverseKey == it.traverseKey && areObjectsOfSameType(this, it)) {
+                @Suppress("UNCHECKED_CAST")
+                block(it as T)
+            } else {
+                true
+            }
+        if (!continueTraversal) return
+    }
+}
+
+// *********** Traverse Children methods ***********
+/**
+ * Executes [block] for all direct children of the node with a matching [key].
+ *
+ * Note 1: This stops at the children and does not include grandchildren and so on down the tree.
+ *
+ * Note 2: The parameter [block]'s return boolean value will determine if the traversal will
+ * continue (true = continue, false = cancel).
+ */
+fun DelegatableNode.traverseChildrenWithKey(
+    key: Any?,
+    block: (TraversableNode) -> Boolean
+) {
+    visitChildren(Nodes.Traversable) {
+        val continueTraversal = if (key == it.traverseKey) {
+            block(it)
+        } else {
+            true
+        }
+        if (!continueTraversal) return
+    }
+}
+
+/**
+ * Executes [block] for all direct children of the node that are of the same class.
+ *
+ * Note 1: This stops at the children and does not include grandchildren and so on down the tree.
+ *
+ * Note 2: The parameter [block]'s return boolean value will determine if the traversal will
+ * continue (true = continue, false = cancel).
+ */
+fun <T> T.traverseChildren(block: (T) -> Boolean) where T : TraversableNode {
+    visitChildren(Nodes.Traversable) {
+        val continueTraversal =
+            if (this.traverseKey == it.traverseKey && areObjectsOfSameType(this, it)) {
+                @Suppress("UNCHECKED_CAST")
+                block(it as T)
+            } else {
+                true
+            }
+        if (!continueTraversal) return
+    }
+}
+
+// *********** Traverse Subtree methods ***********
+/**
+ * Executes [block] for all nodes in the subtree with a matching [key].
+ *
+ * Note: The parameter [block]'s return boolean value will determine if the traversal will
+ * continue (true = continue, false = cancel).
+ */
+fun DelegatableNode.traverseSubtreeWithKey(
+    key: Any?,
+    block: (TraversableNode) -> Boolean
+) {
+    visitSubtree(Nodes.Traversable) {
+        val continueTraversal = if (key == it.traverseKey) {
+            block(it)
+        } else {
+            true
+        }
+        if (!continueTraversal) return
+    }
+}
+
+/**
+ * Executes [block] for all nodes of the same class in the subtree.
+ *
+ * Note: The parameter [block]'s return boolean value will determine if the traversal will
+ * continue (true = continue, false = cancel).
+ */
+fun <T> T.traverseSubtree(block: (T) -> Boolean) where T : TraversableNode {
+    visitSubtree(Nodes.Traversable) {
+        val continueTraversal =
+            if (this.traverseKey == it.traverseKey && areObjectsOfSameType(this, it)) {
+                @Suppress("UNCHECKED_CAST")
+                block(it as T)
+            } else {
+                true
+            }
+        if (!continueTraversal) return
+    }
+}
+
+// *********** Traverse Subtree If methods ***********
+/**
+ * Conditionally executes [block] for each node with a matching [key] in the subtree.
+ *
+ * Note 1: For nodes that do not have the same key, it will continue to execute the [block] for
+ * the subtree below that non-matching node (where there may be a node that matches).
+ *
+ * Note 2: The parameter [block]'s return value [VisitSubtreeIfAction] will determine the next step
+ * in the traversal.
+ */
+fun DelegatableNode.traverseSubtreeIfWithKey(
+    key: Any?,
+    block: (TraversableNode) -> VisitSubtreeIfAction
+) {
+    visitSubtreeIf(Nodes.Traversable) {
+        val action = if (key == it.traverseKey) {
+            block(it)
+        } else {
+            VisitSubtreeIfAction.VisitSubtree
+        }
+        if (action == VisitSubtreeIfAction.CancelTraversal) return
+
+        // visitSubtreeIf() requires a true to continue down the subtree and a false if you
+        // want to skip the subtree, so we check if the action is NOT EQUAL to the subtree
+        // to trigger false if the action is Skip subtree and true otherwise.
+        action != VisitSubtreeIfAction.SkipSubtree
+    }
+}
+
+/**
+ * Conditionally executes [block] for each node of the same class in the subtree.
+ *
+ * Note 1: For nodes that do not have the same key, it will continue to execute the [block] for
+ * the subtree below that non-matching node (where there may be a node that matches).
+ *
+ * Note 2: The parameter [block]'s return value [VisitSubtreeIfAction] will determine the next step
+ * in the traversal.
+ */
+fun <T> T.traverseSubtreeIf(block: (T) -> VisitSubtreeIfAction) where T : TraversableNode {
+    visitSubtreeIf(Nodes.Traversable) {
+        val action =
+            if (this.traverseKey == it.traverseKey && areObjectsOfSameType(this, it)) {
+                @Suppress("UNCHECKED_CAST")
+                block(it as T)
+            } else {
+                VisitSubtreeIfAction.VisitSubtree
+            }
+        if (action == VisitSubtreeIfAction.CancelTraversal) return
+
+        // visitSubtreeIf() requires a true to continue down the subtree and a false if you
+        // want to skip the subtree, so we check if the action is NOT EQUAL to the subtree
+        // to trigger false if the action is Skip subtree and true otherwise.
+        action != VisitSubtreeIfAction.SkipSubtree
+    }
+}
diff --git a/core/core-performance-play-services/build.gradle b/core/core-performance-play-services/build.gradle
index c7a44dd..5ae6276 100644
--- a/core/core-performance-play-services/build.gradle
+++ b/core/core-performance-play-services/build.gradle
@@ -29,23 +29,13 @@
     implementation(libs.kotlinCoroutinesCore)
     implementation(project(":core:core-performance"))
     implementation("androidx.datastore:datastore-preferences:1.0.0")
-    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
 
-    testImplementation(libs.robolectric)
     androidTestImplementation(libs.junit)
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.truth)
-    androidTestImplementation(libs.espressoCore, excludes.espresso)
-    androidTestImplementation(libs.mockitoAndroid)
 
 
-    testImplementation(libs.testCore)
-    testImplementation(libs.kotlinStdlib)
-    testImplementation(libs.kotlinCoroutinesTest)
-    testImplementation(libs.junit)
-    testImplementation(libs.truth)
-
 }
 
 android {
diff --git a/core/core-performance-play-services/src/androidTest/java/androidx/core/performance/play/services/PlayServiceDevicePerformanceAndroidTest.kt b/core/core-performance-play-services/src/androidTest/java/androidx/core/performance/play/services/PlayServiceDevicePerformanceAndroidTest.kt
index 033e68a..26ed265 100644
--- a/core/core-performance-play-services/src/androidTest/java/androidx/core/performance/play/services/PlayServiceDevicePerformanceAndroidTest.kt
+++ b/core/core-performance-play-services/src/androidTest/java/androidx/core/performance/play/services/PlayServiceDevicePerformanceAndroidTest.kt
@@ -28,27 +28,26 @@
 import com.google.android.gms.common.api.internal.ApiKey
 import com.google.android.gms.deviceperformance.DevicePerformanceClient
 import com.google.android.gms.tasks.Task
-import com.google.android.gms.tasks.Tasks
+import com.google.android.gms.tasks.TaskCompletionSource
 import com.google.common.truth.Truth
 import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.runBlocking
 import org.junit.After
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when`
 
 /** Android Unit tests for [PlayServicesDevicePerformance]. */
 @RunWith(AndroidJUnit4::class)
 class PlayServicesDevicePerformanceTest {
-    open class DevicePerformanceClientTest : DevicePerformanceClient {
+    class FakeDevicePerformanceClient() : DevicePerformanceClient {
+        val taskSource: TaskCompletionSource<Int> = TaskCompletionSource()
         override fun getApiKey(): ApiKey<Api.ApiOptions.NoOptions> {
             // method for testing purpose
             return this.apiKey
         }
 
         override fun mediaPerformanceClass(): Task<Int> {
-            return Tasks.forResult(0)
+            return taskSource.task
         }
     }
 
@@ -62,45 +61,26 @@
 
     @Test
     @MediumTest
-    fun basePlayServiceDevicePerformanceClassTest() {
-        val playServicesDevicePerformance = PlayServicesDevicePerformance(
-            context
-        )
-        val pcScore = playServicesDevicePerformance.mediaPerformanceClass
-        Truth.assertThat(pcScore).isEqualTo(defaultMediaPerformanceClass)
-    }
+    fun mediaPerformanceClass_EmptyStore_33Client() {
+        val fakeDevicePerformanceClient = FakeDevicePerformanceClient()
 
-    @Test
-    @MediumTest
-    fun mockPlayServiceDevicePerformanceClassTest() {
-        val mockClient: DevicePerformanceClient = mock(DevicePerformanceClientTest::class.java)
-        val mediaPerformanceClass = 33
-        `when`(mockClient.mediaPerformanceClass()).thenAnswer {
-            Tasks.forResult(mediaPerformanceClass)
-        }
-        val playServicesDevicePerformance = PlayServicesDevicePerformance(
+        val playServicesDevicePerformance = PlayServicesDevicePerformance.create(
             context,
-            mockClient
+            fakeDevicePerformanceClient
         )
+        fakeDevicePerformanceClient.taskSource.setResult(33)
         delayRead()
         val pcScore = playServicesDevicePerformance.mediaPerformanceClass
-        Truth.assertThat(pcScore).isEqualTo(mediaPerformanceClass)
+        Truth.assertThat(pcScore).isEqualTo(33)
     }
 
     @Test
     @MediumTest
-    fun delayMockPlayServiceDevicePerformanceClassTest() {
-        val mockClient: DevicePerformanceClient = mock(DevicePerformanceClientTest::class.java)
-
-        // Delay the response from mockClient.mediaPerformanceClass() so
-        // response will be different that provided.
-        `when`(mockClient.mediaPerformanceClass()).thenAnswer {
-            TimeUnit.SECONDS.sleep(5)
-            Tasks.forResult(defaultMediaPerformanceClass + 100)
-        }
-        val playServicesDevicePerformance = PlayServicesDevicePerformance(
+    fun mediaPerformanceClass_EmptyStore() {
+        val fakeDevicePerformanceClient = FakeDevicePerformanceClient()
+        val playServicesDevicePerformance = PlayServicesDevicePerformance.create(
             context,
-            mockClient
+            fakeDevicePerformanceClient
         )
         val pcScore = playServicesDevicePerformance.mediaPerformanceClass
         Truth.assertThat(pcScore).isEqualTo(defaultMediaPerformanceClass)
@@ -108,32 +88,30 @@
 
     @Test
     @MediumTest
-    fun playServiceCrashPerformanceClassTest() {
-        val mockClient: DevicePerformanceClient = mock(DevicePerformanceClientTest::class.java)
-        `when`(mockClient.mediaPerformanceClass()).thenReturn( // Throw an exception here.
-            Tasks.forException(IllegalStateException())
-        )
-        val pc = PlayServicesDevicePerformance(
+    fun mediaPerformanceClass_EmptyStore_IllegalStateException() {
+        val fakeDevicePerformanceClient = FakeDevicePerformanceClient()
+        fakeDevicePerformanceClient.taskSource.setException(IllegalStateException())
+        val playServicesDevicePerformance = PlayServicesDevicePerformance.create(
             context,
-            mockClient
+            fakeDevicePerformanceClient
         )
         // Since the gms service has crashed, the library should still return default value.
-        Truth.assertThat(pc.mediaPerformanceClass).isEqualTo(defaultMediaPerformanceClass)
+        Truth.assertThat(playServicesDevicePerformance.mediaPerformanceClass)
+            .isEqualTo(defaultMediaPerformanceClass)
     }
 
     @Test
     @MediumTest
-    fun playServiceNotStartPerformanceClassTest() {
-        val mockClient: DevicePerformanceClient = mock(DevicePerformanceClientTest::class.java)
-        `when`(mockClient.mediaPerformanceClass()).thenReturn( // Throw an exception here.
-            Tasks.forException(ApiException(Status.RESULT_TIMEOUT))
-        )
-        val pc = PlayServicesDevicePerformance(
+    fun mediaPerformanceClass_EmptyStore_TimeOut() {
+        val fakeDevicePerformanceClient = FakeDevicePerformanceClient()
+        fakeDevicePerformanceClient.taskSource.setException(ApiException(Status.RESULT_TIMEOUT))
+        val playServicesDevicePerformance = PlayServicesDevicePerformance.create(
             context,
-            mockClient
+            fakeDevicePerformanceClient
         )
         // Since the gms service not started, the library should still return default value.
-        Truth.assertThat(pc.mediaPerformanceClass).isEqualTo(defaultMediaPerformanceClass)
+        Truth.assertThat(playServicesDevicePerformance.mediaPerformanceClass)
+            .isEqualTo(defaultMediaPerformanceClass)
     }
 
     /* Add delay to make sure that value is written in Preference datastore before reading it */
diff --git a/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformance.kt b/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformance.kt
index 2fa0a804..03f2d6a 100644
--- a/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformance.kt
+++ b/core/core-performance-play-services/src/main/java/androidx/core/performance/play/services/PlayServicesDevicePerformance.kt
@@ -39,7 +39,9 @@
  *
  * @param context The application context value to use.
  */
-class PlayServicesDevicePerformance(private val context: Context) : DevicePerformance {
+class PlayServicesDevicePerformance
+private constructor(private val context: Context, client: DevicePerformanceClient) :
+    DevicePerformance {
     private val tag = "PlayServicesDevicePerformance"
 
     private val defaultMpc = DefaultDevicePerformance()
@@ -64,24 +66,32 @@
             "Getting mediaPerformanceClass from " +
                 "com.google.android.gms.deviceperformance.DevicePerformanceClient"
         )
-        updatePerformanceStore(
-            com.google.android.gms.deviceperformance.DevicePerformance.getClient(context)
-        )
+        updatePerformanceStore(client)
     }
 
-    @VisibleForTesting
-    internal constructor(context: Context, client: DevicePerformanceClient) : this(context) {
-        // mock client should wait for the playServices client to finish,
-        // so the test results are determined by the mock client.
-        runBlocking {
-            playServicesValueStoredDeferred.await()
-        }
-        updatePerformanceStore(client)
+    /**
+     * A DevicePerformance that uses Google Play Services to retrieve media performance class data.
+     *
+     * @param context The application context value to use.
+     */
+      constructor(context: Context) : this(
+        context,
+        com.google.android.gms.deviceperformance.DevicePerformance.getClient(context)
+    ) {
     }
 
     private val mpcKey = intPreferencesKey("mpc_value")
 
     internal companion object {
+
+        @VisibleForTesting
+        fun create(
+            context: Context,
+            client: DevicePerformanceClient
+        ): PlayServicesDevicePerformance {
+            return PlayServicesDevicePerformance(context, client)
+        }
+
         // To avoid creating multiple instance of datastore
         private val Context.performanceStore by
         preferencesDataStore(name = "media_performance_class")
@@ -115,16 +125,14 @@
                 launch {
                     savePerformanceClass(storedVal)
                     Log.v(tag, "Saved mediaPerformanceClass $storedVal")
-                    playServicesValueStoredDeferred.complete(true)
                 }
             }
         }.addOnFailureListener { e: Exception ->
             if (e is ApiException) {
-                Log.e(tag, "Error saving mediaPerformanceClass: $e")
+                Log.e(tag, "Error saving mediaPerformanceClass", e)
             } else if (e is IllegalStateException) {
-                Log.e(tag, "Error saving mediaPerformanceClass: $e")
+                Log.e(tag, "Error saving mediaPerformanceClass", e)
             }
-            playServicesValueStoredDeferred.complete(true)
         }
     }
 }
diff --git a/core/core-telecom/api/current.txt b/core/core-telecom/api/current.txt
index b01e13c..4bc7ae5 100644
--- a/core/core-telecom/api/current.txt
+++ b/core/core-telecom/api/current.txt
@@ -88,3 +88,10 @@
 
 }
 
+package androidx.core.telecom.util {
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalAppActions {
+  }
+
+}
+
diff --git a/core/core-telecom/api/restricted_current.txt b/core/core-telecom/api/restricted_current.txt
index b01e13c..4bc7ae5 100644
--- a/core/core-telecom/api/restricted_current.txt
+++ b/core/core-telecom/api/restricted_current.txt
@@ -88,3 +88,10 @@
 
 }
 
+package androidx.core.telecom.util {
+
+  @SuppressCompatibility @RequiresOptIn @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalAppActions {
+  }
+
+}
+
diff --git a/core/core-telecom/build.gradle b/core/core-telecom/build.gradle
index c186a3b..047deba 100644
--- a/core/core-telecom/build.gradle
+++ b/core/core-telecom/build.gradle
@@ -27,6 +27,8 @@
     api(libs.kotlinStdlib)
     api(libs.guavaListenableFuture)
     implementation("androidx.annotation:annotation:1.4.0")
+    // @OptIn annotations
+    api("androidx.annotation:annotation-experimental:1.3.0")
     implementation("androidx.core:core:1.9.0")
     implementation(libs.kotlinCoroutinesCore)
     implementation(libs.kotlinCoroutinesGuava)
@@ -45,6 +47,9 @@
 
 android {
     namespace "androidx.core.telecom"
+    buildFeatures {
+        aidl = true
+    }
 }
 
 androidx {
diff --git a/core/core-telecom/src/androidTest/AndroidManifest.xml b/core/core-telecom/src/androidTest/AndroidManifest.xml
index 6e24bd3..4d94ecb2 100644
--- a/core/core-telecom/src/androidTest/AndroidManifest.xml
+++ b/core/core-telecom/src/androidTest/AndroidManifest.xml
@@ -16,6 +16,7 @@
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
     <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
+    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
 
     <application>
         <service
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/E2ECallExtensionExtrasTests.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/E2ECallExtensionExtrasTests.kt
new file mode 100644
index 0000000..458a920
--- /dev/null
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/E2ECallExtensionExtrasTests.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.test
+
+import android.Manifest
+import android.os.Build
+import android.telecom.Call
+import android.telecom.DisconnectCause
+import androidx.annotation.RequiresApi
+import androidx.core.telecom.CallAttributesCompat
+import androidx.core.telecom.CallsManager
+import androidx.core.telecom.internal.InCallServiceCompat
+import androidx.core.telecom.internal.utils.Utils
+import androidx.core.telecom.test.utils.BaseTelecomTest
+import androidx.core.telecom.test.utils.TestUtils
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.rule.GrantPermissionRule
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This test class helps verify the E2E behavior for calls added via Jetpack to ensure that the
+ * call details contain the appropriate extension extras that define the support for capability
+ * exchange between the VOIP app and ICS.
+ *
+ * Note: Currently, this test only verifies the presence of [CallsManager.PROPERTY_IS_TRANSACTIONAL]
+ * (only in V) in the call properties, if the phone account supports transactional ops (U+ devices),
+ * or if the [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED] key is present in the call
+ * extras (pre-U devices). In the future, this will be expanded to be provide more robust testing
+ * to verify binder functionality as well as supporting the case for auto
+ * ([CallsManager.EXTRA_VOIP_API_VERSION]).
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@RequiresApi(Build.VERSION_CODES.O)
+@RunWith(AndroidJUnit4::class)
+class E2ECallExtensionExtrasTests : BaseTelecomTest() {
+    private lateinit var inCallServiceCompat: InCallServiceCompat
+
+    /**
+     * Grant READ_PHONE_NUMBERS permission as part of testing
+     * [InCallServiceCompat#resolveCallExtensionsType].
+     */
+    @get:Rule
+    val readPhoneNumbersRule: GrantPermissionRule =
+        GrantPermissionRule.grant(Manifest.permission.READ_PHONE_NUMBERS)!!
+
+    @Before
+    fun setUp() {
+        Utils.resetUtils()
+        inCallServiceCompat = InCallServiceCompat(mContext)
+    }
+
+    @After
+    fun onDestroy() {
+        Utils.resetUtils()
+    }
+
+    /***********************************************************************************************
+     *                           V2 APIs (Android U and above) tests
+     *********************************************************************************************/
+
+    /**
+     * For U+ devices using the v2 APIs, assert that the incoming call details either support
+     * the [CallsManager.PROPERTY_IS_TRANSACTIONAL] property (V) or the phone account supports
+     * transactional operations (U+).
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @LargeTest
+    @Test(timeout = 10000)
+    fun testCapabilityExchangeIncoming_V2() {
+        setUpV2Test()
+        addAndVerifyCallExtensionTypeE2E(TestUtils.INCOMING_CALL_ATTRIBUTES)
+    }
+
+    /**
+     * For U+ devices using the v2 APIs, assert that the outgoing call details either support
+     * the [CallsManager.PROPERTY_IS_TRANSACTIONAL] property (V) or the phone account supports
+     * transactional operations (U+).
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @LargeTest
+    @Test(timeout = 10000)
+    fun testCapabilityExchangeOutgoing_V2() {
+        setUpV2Test()
+        addAndVerifyCallExtensionTypeE2E(TestUtils.OUTGOING_CALL_ATTRIBUTES)
+    }
+
+    /***********************************************************************************************
+     *                           Backwards Compatibility Layer tests
+     *********************************************************************************************/
+
+    /**
+     * For pre-U devices using the backwards compatibility library, assert that the incoming call
+     * details contain the [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED] key
+     */
+    @LargeTest
+    @Test(timeout = 10000)
+    fun testCapabilityExchangeIncoming_BackwardsCompat() {
+        setUpBackwardsCompatTest()
+        addAndVerifyCallExtensionTypeE2E(
+            TestUtils.INCOMING_CALL_ATTRIBUTES,
+            waitForCallDetailExtras = true
+        )
+    }
+
+    /**
+     * For pre-U devices using the backwards compatibility library, assert that the outgoing call
+     * details contain the [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED] key
+     */
+    @LargeTest
+    @Test(timeout = 10000)
+    fun testCapabilityExchangeOutgoing_BackwardsCompat() {
+        setUpBackwardsCompatTest()
+        addAndVerifyCallExtensionTypeE2E(
+            TestUtils.OUTGOING_CALL_ATTRIBUTES,
+            waitForCallDetailExtras = true
+        )
+    }
+
+    /***********************************************************************************************
+     *                           Helpers
+     *********************************************************************************************/
+
+    /**
+     * Helper to add a call via CallsManager#addCall and block (if needed) until the connection
+     * extras are propagated into the call details.
+     *
+     * @param callAttributesCompat for the call.
+     * @param waitForCallDetailExtras used for waiting on the call details extras to be non-empty.
+     */
+    private fun addAndVerifyCallExtensionTypeE2E(
+        callAttributesCompat: CallAttributesCompat,
+        waitForCallDetailExtras: Boolean = false
+    ) {
+        runBlocking {
+            assertWithinTimeout_addCall(callAttributesCompat) {
+                launch {
+                    val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
+                    Assert.assertNotNull("The returned Call object is <NULL>", call!!)
+
+                    // Enforce waiting logic to ensure that the call details extras are populated.
+                    if (waitForCallDetailExtras) {
+                        TestUtils.waitOnCallExtras(call)
+                    }
+
+                    // Assert the call extra or call property from the details
+                    assertCallExtraOrProperty(call)
+                    // Always send disconnect signal if possible.
+                    assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
+                }
+            }
+        }
+    }
+
+    /**
+     * Helper to assert the call extra or property set on the call coming from Telecom.
+     */
+    private fun assertCallExtraOrProperty(call: Call) {
+        // Call details should be present at this point
+        val callDetails = call.details!!
+        if (TestUtils.buildIsAtLeastU()) {
+            if (TestUtils.buildIsAtLeastV()) {
+                assertTrue(callDetails.hasProperty(CallsManager.PROPERTY_IS_TRANSACTIONAL))
+            } else if (Utils.hasPlatformV2Apis()) {
+                // We need to check the phone account, which requires accessing TelecomManager.
+                // Directly resolving the extension type via resolveCallExtensionsType() will
+                // provide that functionality so no need to rewrite it here.
+                assertEquals(
+                    inCallServiceCompat.resolveCallExtensionsType(call),
+                    InCallServiceCompat.CAPABILITY_EXCHANGE)
+            }
+        } else {
+            val containsBackwardsCompatKey = callDetails.extras != null && callDetails.extras
+                .containsKey(CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED)
+            assertTrue(containsBackwardsCompatKey)
+        }
+    }
+}
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/ExtensionAidlTest.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/ExtensionAidlTest.kt
new file mode 100644
index 0000000..d2afbba
--- /dev/null
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/ExtensionAidlTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.test
+
+import androidx.core.telecom.extensions.Capability
+import androidx.core.telecom.extensions.ICapabilityExchange
+import androidx.core.telecom.extensions.ICapabilityExchangeListener
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Not very useful for now, but tests the visibility of the AIDL files and ensures that they can
+ * be used as described.
+ */
+@RunWith(AndroidJUnit4::class)
+class ExtensionAidlTest {
+
+    class CapabilityExchangeImpl(
+        val onSetListener: (ICapabilityExchangeListener?) -> Unit = {},
+        val onNegotiateCapabilities: (MutableList<Capability>) -> Unit = {},
+        val onFeatureSetupComplete: () -> Unit = {}
+    ) : ICapabilityExchange.Stub() {
+        override fun setListener(l: ICapabilityExchangeListener?) {
+            onSetListener(l)
+        }
+
+        override fun negotiateCapabilities(capabilities: MutableList<Capability>?) {
+            capabilities?.let {
+                onNegotiateCapabilities(capabilities)
+            }
+        }
+
+        override fun featureSetupComplete() {
+            onFeatureSetupComplete()
+        }
+    }
+
+    class CapabilityExchangeListenerImpl(
+        val capabilitiesNegotiated: (MutableList<Capability>) -> Unit = {}
+    ) : ICapabilityExchangeListener.Stub() {
+        override fun onCapabilitiesNegotiated(filteredCapabilities: MutableList<Capability>?) {
+            filteredCapabilities?.let {
+                capabilitiesNegotiated(filteredCapabilities)
+            }
+        }
+    }
+
+    @SmallTest
+    @Test
+    fun testSetListener() {
+        // setup interfaces
+        var listener: ICapabilityExchangeListener? = null
+        val capExchange = CapabilityExchangeImpl(onSetListener = {
+            listener = it
+        })
+        val capExchangeListener = CapabilityExchangeListenerImpl()
+
+        // set the listener to non-null value
+        capExchange.setListener(capExchangeListener)
+        assertEquals(capExchangeListener, listener)
+
+        // set back to null value
+        capExchange.setListener(null)
+        assertNull(listener)
+    }
+
+    @SmallTest
+    @Test
+    fun testNegotiateCapabilities() {
+        // setup
+        var listener: ICapabilityExchangeListener? = null
+        var capabilities: MutableList<Capability>? = null
+        var filteredCapabilities: MutableList<Capability>? = null
+        val capExchange = CapabilityExchangeImpl(onSetListener = {
+             listener = it
+        }, onNegotiateCapabilities = {
+            capabilities = it
+        })
+        capExchange.onSetListener(CapabilityExchangeListenerImpl {
+            filteredCapabilities = it
+        })
+        val testCapability = Capability()
+        testCapability.featureVersion = 2
+        testCapability.featureId = 1
+        testCapability.supportedActions = intArrayOf(1, 2)
+        val testCapabilities = mutableListOf(testCapability)
+
+        // Send caps
+        capExchange.negotiateCapabilities(testCapabilities)
+        assertEquals(testCapabilities, capabilities)
+        assertNotNull(listener)
+
+        // Receive filtered caps
+        listener?.onCapabilitiesNegotiated(testCapabilities)
+        assertEquals(testCapabilities, filteredCapabilities)
+    }
+
+    @SmallTest
+    @Test
+    fun testSetupComplete() {
+        // setup
+        var isComplete = false
+        val capExchange = CapabilityExchangeImpl(onFeatureSetupComplete = {
+            isComplete = true
+        })
+
+        // ensure feature setup complete is called properly
+        capExchange.featureSetupComplete()
+        assertTrue(isComplete)
+    }
+}
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/InCallServiceCompatTest.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/InCallServiceCompatTest.kt
new file mode 100644
index 0000000..94f3a2e
--- /dev/null
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/InCallServiceCompatTest.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.test
+
+import android.Manifest
+import android.os.Build
+import android.telecom.Call
+import android.telecom.DisconnectCause
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.core.telecom.CallAttributesCompat
+import androidx.core.telecom.CallsManager
+import androidx.core.telecom.internal.InCallServiceCompat
+import androidx.core.telecom.internal.utils.Utils
+import androidx.core.telecom.test.utils.BaseTelecomTest
+import androidx.core.telecom.test.utils.TestUtils
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.rule.GrantPermissionRule
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This test class verifies the [InCallServiceCompat] functionality around resolving the call
+ * extension type in order to determine the supported extensions between the VOIP app and the
+ * associated InCallServices. This test constructs calls via TelecomManager and modifies the call
+ * details (if required) to test each scenario. This is explained in more detail at the test level
+ * for each of the applicable cases below.
+ *
+ * Note: [Call] is package-private so we still need to leverage Telecom to create calls on our
+ * behalf for testing. The call properties and extras fields aren't mutable so we need to ensure
+ * that we wait for them to become available before accessing them.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@RequiresApi(Build.VERSION_CODES.O)
+@RunWith(AndroidJUnit4::class)
+class InCallServiceCompatTest : BaseTelecomTest() {
+    private lateinit var inCallServiceCompat: InCallServiceCompat
+
+    /**
+     * Grant READ_PHONE_NUMBERS permission as part of testing
+     * [InCallServiceCompat#resolveCallExtensionsType].
+     */
+    @get:Rule
+    val readPhoneNumbersRule: GrantPermissionRule =
+        GrantPermissionRule.grant(Manifest.permission.READ_PHONE_NUMBERS)!!
+
+    companion object {
+        /**
+         * Logging for within the test class.
+         */
+        internal val TAG = InCallServiceCompatTest::class.simpleName
+    }
+
+    @Before
+    fun setUp() {
+        Utils.resetUtils()
+        inCallServiceCompat = InCallServiceCompat(mContext)
+    }
+
+    @After
+    fun onDestroy() {
+        Utils.resetUtils()
+    }
+
+    /**
+     * Assert that EXTRAS is the extension type for calls made using the V1.5 ConnectionService +
+     * Extensions Library (Auto). The call should have the [CallsManager.EXTRA_VOIP_API_VERSION]
+     * defined in the extras.
+     *
+     * The contents of the call detail extras need to be modified to test calls using the V1.5
+     * ConnectionService + Extensions library (until E2E testing can be supported for it). This
+     * requires us to manually insert the [CallsManager.EXTRA_VOIP_API_VERSION] key into the bundle.
+     */
+    @LargeTest
+    @Test(timeout = 10000)
+    fun testResolveCallExtension_Extra() {
+        setUpBackwardsCompatTest()
+        val voipApiExtra = Pair(CallsManager.EXTRA_VOIP_API_VERSION, true)
+        addAndVerifyCallExtensionType(
+            TestUtils.OUTGOING_CALL_ATTRIBUTES,
+            InCallServiceCompat.EXTRAS,
+            extraToInclude = voipApiExtra)
+    }
+
+    /**
+     * Assert that CAPABILITY_EXCHANGE is the extension type for calls that either have the
+     * [CallsManager.PROPERTY_IS_TRANSACTIONAL] (V) defined as a property or the phone account
+     * supports transactional ops (U+). For pre-U devices, the call extras would define the
+     * [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED] key.
+     *
+     * Note: The version codes for V is not available so we need to enforce a strict manual check
+     * to ensure the V test path is not executed by incompatible devices.
+     */
+    @LargeTest
+    @Test(timeout = 10000)
+    fun testResolveCallExtension_CapabilityExchange() {
+        if (TestUtils.buildIsAtLeastU()) {
+            Log.w(TAG, "Setting up v2 tests for U+ device")
+            setUpV2Test()
+        } else {
+            Log.w(TAG, "Setting up backwards compatibility tests for pre-U device")
+            setUpBackwardsCompatTest()
+        }
+
+        // Add EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED for pre-U testing
+        val backwardsCompatExtra = if (!TestUtils.buildIsAtLeastU())
+            Pair(CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED, true)
+        else null
+        addAndVerifyCallExtensionType(
+            TestUtils.OUTGOING_CALL_ATTRIBUTES,
+            InCallServiceCompat.CAPABILITY_EXCHANGE,
+            // Waiting is not required for U+ testing
+            waitForCallDetailExtras = !TestUtils.buildIsAtLeastU(),
+            extraToInclude = backwardsCompatExtra
+        )
+    }
+
+    /**
+     * Assert that NONE is the extension type for calls with phone accounts that do not support
+     * transactional ops. Note that the caller must have had the read phone numbers permission.
+     *
+     * Note: Ensure that all extras are cleared before asserting extension type so that the phone
+     * account can be checked. For backwards compatibility tests, calls define the
+     * [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED] key in the details extras so this
+     * needs to be disregarded.
+     *
+     * We need to ensure that all extras/properties are ignored for testing so that the phone
+     * account can be checked to see if it supports transactional ops. In jetpack, this can only be
+     * verified on pre-U devices as those phone accounts are registered in Telecom without
+     * transactional ops. Keep in mind that because these calls are set up for backwards
+     * compatibility, they will have the [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED]
+     * extra in the details (which will need to be ignored during testing).
+     */
+    @LargeTest
+    @Test(timeout = 10000)
+    fun testResolveCallExtension_TransactionalOpsNotSupported() {
+        // Phone accounts that don't use the v2 APIs don't support transactional ops.
+        setUpBackwardsCompatTest()
+        addAndVerifyCallExtensionType(
+            TestUtils.OUTGOING_CALL_ATTRIBUTES,
+            InCallServiceCompat.NONE,
+            waitForCallDetailExtras = false
+        )
+    }
+
+    /***********************************************************************************************
+     *                           Helpers
+     *********************************************************************************************/
+
+    /**
+     * Helper to add a call via CallsManager#addCall and verify the extension type depending on
+     * the APIs that are leveraged.
+     *
+     * Note: The connection extras are not added into the call until the connection is successfully
+     * created. This is usually the case when the call moves from the CONNECTING state into either
+     * the DIALING/RINGING state. This would be the case for [CallsManager.EXTRA_VOIP_API_VERSION]
+     * (handled by auto) as well as for [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED]
+     * (see JetpackConnectionService#createSelfManagedConnection). Keep in mind that these extras
+     * would not be available in [InCalLService#onCallAdded], but after
+     * [Call#handleCreateConnectionSuccess] is invoked and the connection service extras are
+     * propagated into the call details via [Call#putConnectionServiceExtras].
+     *
+     * @param callAttributesCompat for the call.
+     * @param expectedType for call extension type.
+     * @param waitForCallDetailExtras used for waiting on the call details extras to be non-null.
+     * @param extraToInclude as part of the call extras.
+     */
+    private fun addAndVerifyCallExtensionType(
+        callAttributesCompat: CallAttributesCompat,
+        @InCallServiceCompat.Companion.CapabilityExchangeType expectedType: Int,
+        waitForCallDetailExtras: Boolean = true,
+        extraToInclude: Pair<String, Boolean>? = null
+    ) {
+        runBlocking {
+            assertWithinTimeout_addCall(callAttributesCompat) {
+                launch {
+                    val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
+                    Assert.assertNotNull("The returned Call object is <NULL>", call!!)
+
+                    // Enforce waiting logic to ensure that the call details extras are populated.
+                    if (waitForCallDetailExtras) {
+                        TestUtils.waitOnCallExtras(call)
+                    }
+
+                    val callDetails = call.details
+                    // Clear out extras to isolate the testing scenarios.
+                    call.details.extras?.clear()
+                    // Add extraToInclude for testing.
+                    if (extraToInclude != null) {
+                        callDetails.extras?.putBoolean(extraToInclude.first, extraToInclude.second)
+                    }
+
+                    // Assert call extension type.
+                    assertEquals(expectedType, inCallServiceCompat.resolveCallExtensionsType(call))
+                    // Always send disconnect signal if possible.
+                    Assert.assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
+                }
+            }
+        }
+    }
+}
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/ManagedConnectionService.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/ManagedConnectionService.kt
index 1d7e7d0..c5d9e8f 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/ManagedConnectionService.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/ManagedConnectionService.kt
@@ -23,6 +23,7 @@
 import android.telecom.PhoneAccountHandle
 import android.telecom.TelecomManager
 import android.telecom.VideoProfile
+import android.util.Log
 import androidx.annotation.RequiresApi
 import androidx.core.telecom.CallAttributesCompat
 import androidx.core.telecom.internal.utils.Utils
@@ -39,6 +40,7 @@
  */
 @RequiresApi(Build.VERSION_CODES.Q)
 class ManagedConnectionService : ConnectionService() {
+    val TAG = ManagedConnectionService::class.simpleName;
     data class PendingConnectionRequest(
         val callAttributes: CallAttributesCompat,
         val completableDeferred: CompletableDeferred<ManagedConnection>?
@@ -53,6 +55,8 @@
         phoneAccountHandle: PhoneAccountHandle,
         pendingConnectionRequest: PendingConnectionRequest,
     ) {
+        Log.i(TAG, "createConnectionRequest: request=[$pendingConnectionRequest]," +
+            " handle=[$phoneAccountHandle]")
         pendingConnectionRequest.callAttributes.mHandle = phoneAccountHandle
 
         // add request to list
@@ -95,6 +99,7 @@
         connectionManagerPhoneAccount: PhoneAccountHandle,
         request: ConnectionRequest
     ) {
+        Log.i(TAG, "onCreateOutgoingConnectionFailed: request=[$request]")
         val pendingRequest: PendingConnectionRequest? = findTargetPendingConnectionRequest(
             request, CallAttributesCompat.DIRECTION_OUTGOING
         )
@@ -119,6 +124,7 @@
         connectionManagerPhoneAccount: PhoneAccountHandle,
         request: ConnectionRequest
     ) {
+        Log.i(TAG, "onCreateIncomingConnectionFailed: request=[$request]")
         val pendingRequest: PendingConnectionRequest? = findTargetPendingConnectionRequest(
             request, CallAttributesCompat.DIRECTION_INCOMING
         )
@@ -130,6 +136,7 @@
         request: ConnectionRequest,
         direction: Int
     ): Connection? {
+        Log.i(TAG, "createSelfManagedConnection: request=[$request], direction=[$direction]")
         val targetRequest: PendingConnectionRequest =
             findTargetPendingConnectionRequest(request, direction) ?: return null
 
@@ -164,7 +171,7 @@
             jetpackConnection.connectionCapabilities =
                 Connection.CAPABILITY_HOLD or Connection.CAPABILITY_SUPPORT_HOLD
         }
-
+        Log.i(TAG, "createSelfManagedConnection: targetRequest=[$targetRequest]")
         targetRequest.completableDeferred?.complete(jetpackConnection)
         mPendingConnectionRequests.remove(targetRequest)
 
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt
index 5aba448..c44414ca 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/utils/TestUtils.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.media.AudioManager
 import android.net.Uri
+import android.os.Build
 import android.os.Build.VERSION_CODES
 import android.os.UserHandle
 import android.os.UserManager
@@ -310,4 +311,39 @@
             )
         }
     }
+
+    /**
+     * Helper to wait on the call detail extras to be populated from the connection service
+     */
+    suspend fun waitOnCallExtras(call: Call) {
+        try {
+            withTimeout(TestUtils.WAIT_ON_CALL_STATE_TIMEOUT) {
+                while (isActive /* aka  within timeout window */ && call.details?.extras == null) {
+                    yield() // another mechanism to stop the while loop if the coroutine is dead
+                    delay(1) // sleep x millisecond(s) instead of spamming check
+                }
+            }
+        } catch (e: TimeoutCancellationException) {
+            Log.i(TestUtils.LOG_TAG, "waitOnCallExtras: timeout reached")
+            TestUtils.dumpTelecom()
+            MockInCallService.destroyAllCalls()
+            throw AssertionError("Expected call detail extras to be non-null.")
+        }
+    }
+
+    /**
+     * Used for testing in V. The build version is not available for referencing so this helper
+     * performs a manual check instead.
+     */
+    fun buildIsAtLeastV(): Boolean {
+        // V is not referencable as a valid build version yet. Enforce strict manual check instead.
+        return Build.VERSION.SDK_INT > 34
+    }
+
+    /**
+     * Determine if the current build supports at least U.
+     */
+    fun buildIsAtLeastU(): Boolean {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+    }
 }
diff --git a/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/Capability.aidl b/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/Capability.aidl
new file mode 100644
index 0000000..f922042
--- /dev/null
+++ b/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/Capability.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.extensions;
+
+@JavaPassthrough(annotation="@androidx.core.telecom.util.ExperimentalAppActions")
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
+parcelable Capability {
+    // ID of the feature (must be unique for each feature)
+    int featureId;
+    // version of the feature (used to ensure compatibility between applications using different
+    // versions of the telecom jetpack library)
+    int featureVersion;
+    // Array of ints which represent the actions associated with the feature that this application
+    // supports.
+    int[] supportedActions;
+}
\ No newline at end of file
diff --git a/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/ICapabilityExchange.aidl b/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/ICapabilityExchange.aidl
new file mode 100644
index 0000000..13f19d5
--- /dev/null
+++ b/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/ICapabilityExchange.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.extensions;
+
+import androidx.core.telecom.extensions.Capability;
+import androidx.core.telecom.extensions.ICapabilityExchangeListener;
+
+@JavaPassthrough(annotation="@androidx.core.telecom.util.ExperimentalAppActions")
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
+oneway interface ICapabilityExchange {
+    const int VERSION = 1;
+
+    // Notify the remote of the singleton listener interface that must be used to perform return
+    // communication.
+    void setListener(ICapabilityExchangeListener l) = 0;
+    // Provide the capabilities of the service and request that capabilities of the remote are
+    // calculated. The response will be signalled back via
+    // ICapabilityExchangeListener#onCapabilitiesNegotiated
+    void negotiateCapabilities(in List<Capability> capabilities) = 1;
+    // All associated extension feature state has been synchronized and the user can now use the
+    // extensions.
+    void featureSetupComplete() = 2;
+}
\ No newline at end of file
diff --git a/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/ICapabilityExchangeListener.aidl b/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/ICapabilityExchangeListener.aidl
new file mode 100644
index 0000000..df9dc27
--- /dev/null
+++ b/core/core-telecom/src/main/aidl/androidx/core/telecom/extensions/ICapabilityExchangeListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.extensions;
+
+import androidx.core.telecom.extensions.Capability;
+
+@JavaPassthrough(annotation="@androidx.core.telecom.util.ExperimentalAppActions")
+@JavaPassthrough(annotation="@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
+oneway interface ICapabilityExchangeListener {
+    // Called to complete the ICapabilityExchange#negotiateCapabilities request with the
+    // capabilities that both the VOIP service and InCallService support.
+    void onCapabilitiesNegotiated(in List<Capability> filteredCapabilities) = 0;
+}
\ No newline at end of file
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt b/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt
index fd67da4..27df20c 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/CallsManager.kt
@@ -82,6 +82,11 @@
         annotation class Capability
 
         /**
+         * Set on Connections that are using ConnectionService+AUTO specific extension layer.
+         */
+        internal const val EXTRA_VOIP_API_VERSION = "android.telecom.extra.VOIP_API_VERSION"
+
+        /**
          * Set on Jetpack Connections that are emulating the transactional APIs using
          * ConnectionService.
          */
@@ -89,6 +94,15 @@
             "android.telecom.extra.VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED"
 
         /**
+         * The connection is using transactional call APIs.
+         *
+         *
+         * The underlying connection was added as a transactional call via the
+         * [TelecomManager.addCall] API.
+         */
+        internal const val PROPERTY_IS_TRANSACTIONAL = 0x00008000
+
+        /**
          * If your VoIP application does not want support any of the capabilities below, then your
          * application can register with [CAPABILITY_BASELINE].
          *
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallCompat.kt
similarity index 63%
copy from camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
copy to core/core-telecom/src/main/java/androidx/core/telecom/internal/CallCompat.kt
index 7a355f8..890707d 100644
--- a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallCompat.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,12 @@
  * limitations under the License.
  */
 
-package androidx.camera.effects;
+package androidx.core.telecom.internal
 
-import androidx.annotation.RestrictTo;
+import android.telecom.Call
+import kotlinx.coroutines.CoroutineScope
 
-/**
- * Provides a portrait post-processing effect.
- *
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class Portrait {
-    // TODO: implement this
+internal class CallCompat(call: Call, block: CoroutineScope.() -> Unit) {
+    private val mCall: Call = call
+    private val mBlock: CoroutineScope.() -> Unit = block
 }
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/InCallServiceCompat.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/InCallServiceCompat.kt
new file mode 100644
index 0000000..f3063c0
--- /dev/null
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/InCallServiceCompat.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.telecom.internal
+
+import android.Manifest
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Bundle
+import android.telecom.Call
+import android.telecom.InCallService
+import android.telecom.PhoneAccount
+import android.telecom.TelecomManager
+import android.util.Log
+import androidx.annotation.IntDef
+import androidx.annotation.RequiresApi
+import androidx.core.content.ContextCompat
+import androidx.core.telecom.CallsManager
+
+/**
+ * This class defines the Jetpack ICS layer which will be leveraged as part of supporting VOIP app
+ * actions.
+ */
+@RequiresApi(Build.VERSION_CODES.M)
+internal class InCallServiceCompat(context: Context) : InCallService() {
+    private val mContext: Context = context
+
+    companion object {
+        /**
+         * Constants used to denote the extension level supported by the VOIP app.
+         */
+        @Retention(AnnotationRetention.SOURCE)
+        @IntDef(NONE, EXTRAS, CAPABILITY_EXCHANGE, UNKNOWN)
+        internal annotation class CapabilityExchangeType
+
+        internal const val NONE = 0
+        internal const val EXTRAS = 1
+        internal const val CAPABILITY_EXCHANGE = 2
+        internal const val UNKNOWN = 3
+
+        private val TAG = InCallServiceCompat::class.simpleName
+    }
+
+    fun onCreateCall(call: Call): CallCompat {
+        Log.d(TAG, "onCreateCall: call = $call")
+        return with(this) {
+            CallCompat(call) {
+            }
+        }
+    }
+
+    fun onRemoveCall(call: CallCompat) {
+        Log.d(TAG, "onRemoveCall: call = $call")
+    }
+
+    /**
+     * Internal helper used by the [InCallService] to help resolve the call extension type. This
+     * is invoked before capability exchange between the [InCallService] and VOIP app starts to
+     * ensure the necessary features are enabled to support it.
+     *
+     * If the call is placed using the V1.5 ConnectionService + Extensions Library (Auto Case), the
+     * call will have the [CallsManager.EXTRA_VOIP_API_VERSION] defined in the extras. The call
+     * extension would be resolved as [InCallServiceCompat.EXTRAS].
+     *
+     * If the call is using the v2 APIs and the phone account associated with the call supports
+     * transactional ops (U+) or the call has the [CallsManager.PROPERTY_IS_TRANSACTIONAL] property
+     * defined (on V devices), then the extension type is [InCallServiceCompat.CAPABILITY_EXCHANGE].
+     *
+     * If the call is added via CallsManager#addCall on pre-U devices and the
+     * [CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED] is present in the call extras,
+     * the extension type also resolves to [InCallServiceCompat.CAPABILITY_EXCHANGE].
+     *
+     * In the case that none of the cases above apply and the phone account is found not to support
+     * transactional ops (assumes that caller has [android.Manifest.permission.READ_PHONE_NUMBERS]
+     * permission), then the extension type is [InCallServiceCompat.NONE].
+     *
+     * If the caller does not have the required permission to retrieve the phone account, then
+     * the extension type will be [InCallServiceCompat.UNKNOWN], until it can be resolved.
+     *
+     * @param call to resolve the extension type for.
+     * @return the extension type [InCallServiceCompat.CapabilityExchangeType] resolved for the
+     * call.
+     */
+    @RequiresApi(Build.VERSION_CODES.O)
+    @CapabilityExchangeType
+    internal fun resolveCallExtensionsType(call: Call): Int {
+        var callDetails = call.details
+        val callExtras = callDetails?.extras ?: Bundle()
+
+        if (callExtras.containsKey(CallsManager.EXTRA_VOIP_API_VERSION)) {
+            return EXTRAS
+        }
+        if (callDetails?.hasProperty(CallsManager.PROPERTY_IS_TRANSACTIONAL) == true || callExtras
+            .containsKey(CallsManager.EXTRA_VOIP_BACKWARDS_COMPATIBILITY_SUPPORTED)) {
+            return CAPABILITY_EXCHANGE
+        }
+        // Verify read phone numbers permission to see if phone account supports transactional ops.
+        if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_PHONE_NUMBERS)
+            == PackageManager.PERMISSION_GRANTED) {
+            var telecomManager = mContext.getSystemService(Context.TELECOM_SERVICE)
+                as TelecomManager
+            var phoneAccount = telecomManager.getPhoneAccount(callDetails?.accountHandle)
+            if (phoneAccount?.hasCapabilities(
+                    PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS) == true) {
+                return CAPABILITY_EXCHANGE
+            } else {
+                return NONE
+            }
+        }
+
+        Log.i(TAG, "Unable to resolve call extension type. Returning $UNKNOWN.")
+        return UNKNOWN
+    }
+}
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java b/core/core-telecom/src/main/java/androidx/core/telecom/util/ExperimentalAppActions.java
similarity index 63%
copy from camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
copy to core/core-telecom/src/main/java/androidx/core/telecom/util/ExperimentalAppActions.java
index 7a355f8..65af69e 100644
--- a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/util/ExperimentalAppActions.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,13 @@
  * limitations under the License.
  */
 
-package androidx.camera.effects;
+package androidx.core.telecom.util;
 
-import androidx.annotation.RestrictTo;
+import androidx.annotation.RequiresOptIn;
 
 /**
- * Provides a portrait post-processing effect.
- *
+ * This API is still experimental. Any features associated with this annotation are unstable and
+ * should not be used in production.
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class Portrait {
-    // TODO: implement this
-}
+@RequiresOptIn
+public @interface ExperimentalAppActions {}
diff --git a/core/haptics/haptics/api/current.txt b/core/haptics/haptics/api/current.txt
index 87cfe87..b4b7e54 100644
--- a/core/haptics/haptics/api/current.txt
+++ b/core/haptics/haptics/api/current.txt
@@ -3,7 +3,7 @@
 
   public interface HapticManager {
     method public static androidx.core.haptics.HapticManager create(android.content.Context context);
-    method @RequiresPermission(android.Manifest.permission.VIBRATE) public void play(androidx.core.haptics.signal.PredefinedEffect effect);
+    method @RequiresPermission(android.Manifest.permission.VIBRATE) public void play(androidx.core.haptics.signal.HapticSignal signal);
     field public static final androidx.core.haptics.HapticManager.Companion Companion;
   }
 
@@ -15,15 +15,155 @@
 
 package androidx.core.haptics.signal {
 
-  public final class PredefinedEffect {
-    field public static final androidx.core.haptics.signal.PredefinedEffect.Companion Companion;
-    field public static final androidx.core.haptics.signal.PredefinedEffect PredefinedClick;
-    field public static final androidx.core.haptics.signal.PredefinedEffect PredefinedDoubleClick;
-    field public static final androidx.core.haptics.signal.PredefinedEffect PredefinedHeavyClick;
-    field public static final androidx.core.haptics.signal.PredefinedEffect PredefinedTick;
+  public final class CompositionSignal extends androidx.core.haptics.signal.FiniteSignal {
+    ctor public CompositionSignal(java.util.List<? extends androidx.core.haptics.signal.CompositionSignal.Atom> atoms);
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom click();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom click(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public static androidx.core.haptics.signal.CompositionSignal compositionOf(androidx.core.haptics.signal.CompositionSignal.Atom... atoms);
+    method public java.util.List<androidx.core.haptics.signal.CompositionSignal.Atom> getAtoms();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom lowTick();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom lowTick(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.core.haptics.signal.CompositionSignal.OffAtom off(java.time.Duration duration);
+    method public static androidx.core.haptics.signal.CompositionSignal.OffAtom off(long durationMillis);
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickFall();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickFall(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickRise();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickRise(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom slowRise();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom slowRise(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom spin();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom spin(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom thud();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom thud(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom tick();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom tick(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    property public final java.util.List<androidx.core.haptics.signal.CompositionSignal.Atom> atoms;
+    field public static final androidx.core.haptics.signal.CompositionSignal.Companion Companion;
   }
 
-  public static final class PredefinedEffect.Companion {
+  public abstract static class CompositionSignal.Atom {
+  }
+
+  public static final class CompositionSignal.Companion {
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom click();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom click(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public androidx.core.haptics.signal.CompositionSignal compositionOf(androidx.core.haptics.signal.CompositionSignal.Atom... atoms);
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom lowTick();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom lowTick(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.core.haptics.signal.CompositionSignal.OffAtom off(java.time.Duration duration);
+    method public androidx.core.haptics.signal.CompositionSignal.OffAtom off(long durationMillis);
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickFall();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickFall(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickRise();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickRise(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom slowRise();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom slowRise(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom spin();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom spin(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom thud();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom thud(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom tick();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom tick(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+  }
+
+  public static final class CompositionSignal.OffAtom extends androidx.core.haptics.signal.CompositionSignal.Atom {
+    method public long getDurationMillis();
+    property public final long durationMillis;
+  }
+
+  public static final class CompositionSignal.PrimitiveAtom extends androidx.core.haptics.signal.CompositionSignal.Atom {
+    method public float getAmplitudeScale();
+    method public int getType();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom withAmplitudeScale(@FloatRange(from=0.0, to=1.0) float newAmplitudeScale);
+    property public final float amplitudeScale;
+    property public final int type;
+    field public static final int CLICK = 1; // 0x1
+    field public static final androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom.Companion Companion;
+    field public static final int LOW_TICK = 8; // 0x8
+    field public static final int QUICK_FALL = 6; // 0x6
+    field public static final int QUICK_RISE = 4; // 0x4
+    field public static final int SLOW_RISE = 5; // 0x5
+    field public static final int SPIN = 3; // 0x3
+    field public static final int THUD = 2; // 0x2
+    field public static final int TICK = 7; // 0x7
+  }
+
+  public static final class CompositionSignal.PrimitiveAtom.Companion {
+  }
+
+  public abstract class FiniteSignal extends androidx.core.haptics.signal.HapticSignal {
+  }
+
+  public abstract class HapticSignal {
+  }
+
+  public abstract class InfiniteSignal extends androidx.core.haptics.signal.HapticSignal {
+  }
+
+  public final class PredefinedEffectSignal extends androidx.core.haptics.signal.FiniteSignal {
+    method public static androidx.core.haptics.signal.PredefinedEffectSignal predefinedClick();
+    method public static androidx.core.haptics.signal.PredefinedEffectSignal predefinedDoubleClick();
+    method public static androidx.core.haptics.signal.PredefinedEffectSignal predefinedHeavyClick();
+    method public static androidx.core.haptics.signal.PredefinedEffectSignal predefinedTick();
+    field public static final androidx.core.haptics.signal.PredefinedEffectSignal.Companion Companion;
+  }
+
+  public static final class PredefinedEffectSignal.Companion {
+    method public androidx.core.haptics.signal.PredefinedEffectSignal predefinedClick();
+    method public androidx.core.haptics.signal.PredefinedEffectSignal predefinedDoubleClick();
+    method public androidx.core.haptics.signal.PredefinedEffectSignal predefinedHeavyClick();
+    method public androidx.core.haptics.signal.PredefinedEffectSignal predefinedTick();
+  }
+
+  public final class RepeatingWaveformSignal extends androidx.core.haptics.signal.InfiniteSignal {
+    method public androidx.core.haptics.signal.WaveformSignal? getInitialWaveform();
+    method public androidx.core.haptics.signal.WaveformSignal getRepeatingWaveform();
+    property public final androidx.core.haptics.signal.WaveformSignal? initialWaveform;
+    property public final androidx.core.haptics.signal.WaveformSignal repeatingWaveform;
+  }
+
+  public final class WaveformSignal extends androidx.core.haptics.signal.FiniteSignal {
+    ctor public WaveformSignal(java.util.List<? extends androidx.core.haptics.signal.WaveformSignal.Atom> atoms);
+    method public java.util.List<androidx.core.haptics.signal.WaveformSignal.Atom> getAtoms();
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom off(java.time.Duration duration);
+    method public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom off(long durationMillis);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(java.time.Duration duration);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(java.time.Duration duration, @FloatRange(from=0.0, to=1.0) float amplitude);
+    method public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(long durationMillis);
+    method public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(long durationMillis, @FloatRange(from=0.0, to=1.0) float amplitude);
+    method public androidx.core.haptics.signal.RepeatingWaveformSignal repeat();
+    method public static androidx.core.haptics.signal.RepeatingWaveformSignal repeatingWaveformOf(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+    method public androidx.core.haptics.signal.RepeatingWaveformSignal thenRepeat(androidx.core.haptics.signal.WaveformSignal waveformToRepeat);
+    method public androidx.core.haptics.signal.RepeatingWaveformSignal thenRepeat(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+    method public static androidx.core.haptics.signal.WaveformSignal waveformOf(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+    property public final java.util.List<androidx.core.haptics.signal.WaveformSignal.Atom> atoms;
+    field public static final androidx.core.haptics.signal.WaveformSignal.Companion Companion;
+  }
+
+  public abstract static class WaveformSignal.Atom {
+  }
+
+  public static final class WaveformSignal.Companion {
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom off(java.time.Duration duration);
+    method public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom off(long durationMillis);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(java.time.Duration duration);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(java.time.Duration duration, @FloatRange(from=0.0, to=1.0) float amplitude);
+    method public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(long durationMillis);
+    method public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(long durationMillis, @FloatRange(from=0.0, to=1.0) float amplitude);
+    method public androidx.core.haptics.signal.RepeatingWaveformSignal repeatingWaveformOf(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+    method public androidx.core.haptics.signal.WaveformSignal waveformOf(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+  }
+
+  public static final class WaveformSignal.ConstantVibrationAtom extends androidx.core.haptics.signal.WaveformSignal.Atom {
+    method public float getAmplitude();
+    method public long getDurationMillis();
+    property public final float amplitude;
+    property public final long durationMillis;
+    field public static final androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom.Companion Companion;
+    field public static final float DEFAULT_AMPLITUDE = -1.0f;
+  }
+
+  public static final class WaveformSignal.ConstantVibrationAtom.Companion {
   }
 
 }
diff --git a/core/haptics/haptics/api/restricted_current.txt b/core/haptics/haptics/api/restricted_current.txt
index 87cfe87..b4b7e54 100644
--- a/core/haptics/haptics/api/restricted_current.txt
+++ b/core/haptics/haptics/api/restricted_current.txt
@@ -3,7 +3,7 @@
 
   public interface HapticManager {
     method public static androidx.core.haptics.HapticManager create(android.content.Context context);
-    method @RequiresPermission(android.Manifest.permission.VIBRATE) public void play(androidx.core.haptics.signal.PredefinedEffect effect);
+    method @RequiresPermission(android.Manifest.permission.VIBRATE) public void play(androidx.core.haptics.signal.HapticSignal signal);
     field public static final androidx.core.haptics.HapticManager.Companion Companion;
   }
 
@@ -15,15 +15,155 @@
 
 package androidx.core.haptics.signal {
 
-  public final class PredefinedEffect {
-    field public static final androidx.core.haptics.signal.PredefinedEffect.Companion Companion;
-    field public static final androidx.core.haptics.signal.PredefinedEffect PredefinedClick;
-    field public static final androidx.core.haptics.signal.PredefinedEffect PredefinedDoubleClick;
-    field public static final androidx.core.haptics.signal.PredefinedEffect PredefinedHeavyClick;
-    field public static final androidx.core.haptics.signal.PredefinedEffect PredefinedTick;
+  public final class CompositionSignal extends androidx.core.haptics.signal.FiniteSignal {
+    ctor public CompositionSignal(java.util.List<? extends androidx.core.haptics.signal.CompositionSignal.Atom> atoms);
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom click();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom click(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public static androidx.core.haptics.signal.CompositionSignal compositionOf(androidx.core.haptics.signal.CompositionSignal.Atom... atoms);
+    method public java.util.List<androidx.core.haptics.signal.CompositionSignal.Atom> getAtoms();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom lowTick();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom lowTick(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.core.haptics.signal.CompositionSignal.OffAtom off(java.time.Duration duration);
+    method public static androidx.core.haptics.signal.CompositionSignal.OffAtom off(long durationMillis);
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickFall();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickFall(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickRise();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickRise(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom slowRise();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom slowRise(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom spin();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom spin(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom thud();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom thud(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom tick();
+    method public static androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom tick(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    property public final java.util.List<androidx.core.haptics.signal.CompositionSignal.Atom> atoms;
+    field public static final androidx.core.haptics.signal.CompositionSignal.Companion Companion;
   }
 
-  public static final class PredefinedEffect.Companion {
+  public abstract static class CompositionSignal.Atom {
+  }
+
+  public static final class CompositionSignal.Companion {
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom click();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom click(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public androidx.core.haptics.signal.CompositionSignal compositionOf(androidx.core.haptics.signal.CompositionSignal.Atom... atoms);
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom lowTick();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom lowTick(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.core.haptics.signal.CompositionSignal.OffAtom off(java.time.Duration duration);
+    method public androidx.core.haptics.signal.CompositionSignal.OffAtom off(long durationMillis);
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickFall();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickFall(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickRise();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom quickRise(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom slowRise();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom slowRise(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom spin();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom spin(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom thud();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom thud(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom tick();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom tick(optional @FloatRange(from=0.0, to=1.0) float amplitudeScale);
+  }
+
+  public static final class CompositionSignal.OffAtom extends androidx.core.haptics.signal.CompositionSignal.Atom {
+    method public long getDurationMillis();
+    property public final long durationMillis;
+  }
+
+  public static final class CompositionSignal.PrimitiveAtom extends androidx.core.haptics.signal.CompositionSignal.Atom {
+    method public float getAmplitudeScale();
+    method public int getType();
+    method public androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom withAmplitudeScale(@FloatRange(from=0.0, to=1.0) float newAmplitudeScale);
+    property public final float amplitudeScale;
+    property public final int type;
+    field public static final int CLICK = 1; // 0x1
+    field public static final androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom.Companion Companion;
+    field public static final int LOW_TICK = 8; // 0x8
+    field public static final int QUICK_FALL = 6; // 0x6
+    field public static final int QUICK_RISE = 4; // 0x4
+    field public static final int SLOW_RISE = 5; // 0x5
+    field public static final int SPIN = 3; // 0x3
+    field public static final int THUD = 2; // 0x2
+    field public static final int TICK = 7; // 0x7
+  }
+
+  public static final class CompositionSignal.PrimitiveAtom.Companion {
+  }
+
+  public abstract class FiniteSignal extends androidx.core.haptics.signal.HapticSignal {
+  }
+
+  public abstract class HapticSignal {
+  }
+
+  public abstract class InfiniteSignal extends androidx.core.haptics.signal.HapticSignal {
+  }
+
+  public final class PredefinedEffectSignal extends androidx.core.haptics.signal.FiniteSignal {
+    method public static androidx.core.haptics.signal.PredefinedEffectSignal predefinedClick();
+    method public static androidx.core.haptics.signal.PredefinedEffectSignal predefinedDoubleClick();
+    method public static androidx.core.haptics.signal.PredefinedEffectSignal predefinedHeavyClick();
+    method public static androidx.core.haptics.signal.PredefinedEffectSignal predefinedTick();
+    field public static final androidx.core.haptics.signal.PredefinedEffectSignal.Companion Companion;
+  }
+
+  public static final class PredefinedEffectSignal.Companion {
+    method public androidx.core.haptics.signal.PredefinedEffectSignal predefinedClick();
+    method public androidx.core.haptics.signal.PredefinedEffectSignal predefinedDoubleClick();
+    method public androidx.core.haptics.signal.PredefinedEffectSignal predefinedHeavyClick();
+    method public androidx.core.haptics.signal.PredefinedEffectSignal predefinedTick();
+  }
+
+  public final class RepeatingWaveformSignal extends androidx.core.haptics.signal.InfiniteSignal {
+    method public androidx.core.haptics.signal.WaveformSignal? getInitialWaveform();
+    method public androidx.core.haptics.signal.WaveformSignal getRepeatingWaveform();
+    property public final androidx.core.haptics.signal.WaveformSignal? initialWaveform;
+    property public final androidx.core.haptics.signal.WaveformSignal repeatingWaveform;
+  }
+
+  public final class WaveformSignal extends androidx.core.haptics.signal.FiniteSignal {
+    ctor public WaveformSignal(java.util.List<? extends androidx.core.haptics.signal.WaveformSignal.Atom> atoms);
+    method public java.util.List<androidx.core.haptics.signal.WaveformSignal.Atom> getAtoms();
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom off(java.time.Duration duration);
+    method public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom off(long durationMillis);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(java.time.Duration duration);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(java.time.Duration duration, @FloatRange(from=0.0, to=1.0) float amplitude);
+    method public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(long durationMillis);
+    method public static androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(long durationMillis, @FloatRange(from=0.0, to=1.0) float amplitude);
+    method public androidx.core.haptics.signal.RepeatingWaveformSignal repeat();
+    method public static androidx.core.haptics.signal.RepeatingWaveformSignal repeatingWaveformOf(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+    method public androidx.core.haptics.signal.RepeatingWaveformSignal thenRepeat(androidx.core.haptics.signal.WaveformSignal waveformToRepeat);
+    method public androidx.core.haptics.signal.RepeatingWaveformSignal thenRepeat(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+    method public static androidx.core.haptics.signal.WaveformSignal waveformOf(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+    property public final java.util.List<androidx.core.haptics.signal.WaveformSignal.Atom> atoms;
+    field public static final androidx.core.haptics.signal.WaveformSignal.Companion Companion;
+  }
+
+  public abstract static class WaveformSignal.Atom {
+  }
+
+  public static final class WaveformSignal.Companion {
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom off(java.time.Duration duration);
+    method public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom off(long durationMillis);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(java.time.Duration duration);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(java.time.Duration duration, @FloatRange(from=0.0, to=1.0) float amplitude);
+    method public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(long durationMillis);
+    method public androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom on(long durationMillis, @FloatRange(from=0.0, to=1.0) float amplitude);
+    method public androidx.core.haptics.signal.RepeatingWaveformSignal repeatingWaveformOf(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+    method public androidx.core.haptics.signal.WaveformSignal waveformOf(androidx.core.haptics.signal.WaveformSignal.Atom... atoms);
+  }
+
+  public static final class WaveformSignal.ConstantVibrationAtom extends androidx.core.haptics.signal.WaveformSignal.Atom {
+    method public float getAmplitude();
+    method public long getDurationMillis();
+    property public final float amplitude;
+    property public final long durationMillis;
+    field public static final androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom.Companion Companion;
+    field public static final float DEFAULT_AMPLITUDE = -1.0f;
+  }
+
+  public static final class WaveformSignal.ConstantVibrationAtom.Companion {
   }
 
 }
diff --git a/core/haptics/haptics/build.gradle b/core/haptics/haptics/build.gradle
index 203d4db..7dc493a 100644
--- a/core/haptics/haptics/build.gradle
+++ b/core/haptics/haptics/build.gradle
@@ -15,7 +15,6 @@
  */
 
 import androidx.build.LibraryType
-import androidx.build.RunApiTasks
 
 plugins {
     id("AndroidXPlugin")
@@ -33,8 +32,7 @@
 
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testRunner)
-    androidTestImplementation(libs.mockitoCore4)
-    androidTestImplementation(libs.dexmakerMockitoInline)
+    implementation(libs.truth)
 }
 
 android {
diff --git a/core/haptics/haptics/integration-tests/demos/src/main/AndroidManifest.xml b/core/haptics/haptics/integration-tests/demos/src/main/AndroidManifest.xml
index 4c19b57..ea32d19 100644
--- a/core/haptics/haptics/integration-tests/demos/src/main/AndroidManifest.xml
+++ b/core/haptics/haptics/integration-tests/demos/src/main/AndroidManifest.xml
@@ -22,7 +22,7 @@
         android:theme="@style/AppTheme">
         <activity
             android:allowBackup="false"
-            android:name=".HapticSamplesActivity"
+            android:name=".HapticDemosActivity"
             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/core/haptics/haptics/integration-tests/demos/src/main/java/androidx/core/haptics/demos/HapticDemosActivity.kt b/core/haptics/haptics/integration-tests/demos/src/main/java/androidx/core/haptics/demos/HapticDemosActivity.kt
new file mode 100644
index 0000000..b147e5c
--- /dev/null
+++ b/core/haptics/haptics/integration-tests/demos/src/main/java/androidx/core/haptics/demos/HapticDemosActivity.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.demos
+
+import android.os.Bundle
+import android.widget.Button
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.haptics.HapticManager
+import androidx.core.haptics.signal.CompositionSignal.Companion.click
+import androidx.core.haptics.signal.CompositionSignal.Companion.compositionOf
+import androidx.core.haptics.signal.PredefinedEffectSignal.Companion.predefinedClick
+import androidx.core.haptics.signal.WaveformSignal.Companion.off
+import androidx.core.haptics.signal.WaveformSignal.Companion.on
+import androidx.core.haptics.signal.WaveformSignal.Companion.waveformOf
+
+/**
+ * Demonstrations of multiple haptic signal samples.
+ */
+class HapticDemosActivity : AppCompatActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.haptics_demos_activity)
+
+        val hapticManager = HapticManager.create(this)
+        findViewById<Button>(R.id.standard_click_btn).setOnClickListener {
+            hapticManager.play(predefinedClick())
+        }
+        findViewById<Button>(R.id.scaled_click_btn).setOnClickListener {
+            hapticManager.play(compositionOf(click().withAmplitudeScale(0.8f)))
+        }
+        findViewById<Button>(R.id.on_off_pattern_btn).setOnClickListener {
+            hapticManager.play(
+                waveformOf(
+                    on(durationMillis = 350),
+                    off(durationMillis = 250),
+                    on(durationMillis = 350),
+                )
+            )
+        }
+        findViewById<Button>(R.id.repeating_waveform_btn).setOnClickListener {
+            hapticManager.play(
+                waveformOf(
+                    on(durationMillis = 20),
+                    off(durationMillis = 50),
+                    on(durationMillis = 20),
+                ).thenRepeat(
+                    // 500ms off
+                    off(durationMillis = 500),
+                    // 600ms ramp up with 50% increments
+                    on(durationMillis = 100, amplitude = 0.1f),
+                    on(durationMillis = 100, amplitude = 0.15f),
+                    on(durationMillis = 100, amplitude = 0.22f),
+                    on(durationMillis = 100, amplitude = 0.34f),
+                    on(durationMillis = 100, amplitude = 0.51f),
+                    on(durationMillis = 100, amplitude = 0.76f),
+                    // 400ms at max amplitude
+                    on(durationMillis = 400, amplitude = 1f),
+                )
+            )
+        }
+    }
+}
diff --git a/core/haptics/haptics/integration-tests/demos/src/main/java/androidx/core/haptics/demos/HapticSamplesActivity.kt b/core/haptics/haptics/integration-tests/demos/src/main/java/androidx/core/haptics/demos/HapticSamplesActivity.kt
deleted file mode 100644
index 5fb1076..0000000
--- a/core/haptics/haptics/integration-tests/demos/src/main/java/androidx/core/haptics/demos/HapticSamplesActivity.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.core.haptics.demos
-
-import android.os.Bundle
-import android.widget.Button
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.haptics.HapticManager
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedClick
-
-/**
- * Demo with multiple selection of haptic effect samples.
- */
-class HapticSamplesActivity : AppCompatActivity() {
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        setContentView(R.layout.haptic_samples_activity)
-
-        val hapticManager = HapticManager.create(this)
-        findViewById<Button>(R.id.standard_click_btn).setOnClickListener {
-            hapticManager.play(PredefinedClick)
-        }
-    }
-}
diff --git a/core/haptics/haptics/integration-tests/demos/src/main/res/layout/haptic_samples_activity.xml b/core/haptics/haptics/integration-tests/demos/src/main/res/layout/haptics_demos_activity.xml
similarity index 64%
rename from core/haptics/haptics/integration-tests/demos/src/main/res/layout/haptic_samples_activity.xml
rename to core/haptics/haptics/integration-tests/demos/src/main/res/layout/haptics_demos_activity.xml
index 24002ee..8b48bab 100644
--- a/core/haptics/haptics/integration-tests/demos/src/main/res/layout/haptic_samples_activity.xml
+++ b/core/haptics/haptics/integration-tests/demos/src/main/res/layout/haptics_demos_activity.xml
@@ -20,7 +20,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:gravity="center"
-    tools:context="androidx.core.haptics.demos.HapticSamplesActivity">
+    tools:context=".HapticDemosActivity">
 
     <Button
         android:id="@+id/standard_click_btn"
@@ -28,4 +28,22 @@
         android:layout_height="wrap_content"
         android:text="@string/standard_click" />
 
+    <Button
+        android:id="@+id/scaled_click_btn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/scaled_click" />
+
+    <Button
+        android:id="@+id/on_off_pattern_btn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/on_off_pattern" />
+
+    <Button
+        android:id="@+id/repeating_waveform_btn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/repeating_waveform" />
+
 </LinearLayout>
diff --git a/core/haptics/haptics/integration-tests/demos/src/main/res/values/donottranslate-strings.xml b/core/haptics/haptics/integration-tests/demos/src/main/res/values/donottranslate-strings.xml
index 903a0be..e79e071 100644
--- a/core/haptics/haptics/integration-tests/demos/src/main/res/values/donottranslate-strings.xml
+++ b/core/haptics/haptics/integration-tests/demos/src/main/res/values/donottranslate-strings.xml
@@ -17,4 +17,7 @@
 <resources>
     <string name="app_name">Haptic Demos</string>
     <string name="standard_click">Standard Click</string>
+    <string name="scaled_click">Scaled Click</string>
+    <string name="on_off_pattern">On/Off Pattern</string>
+    <string name="repeating_waveform">Repeating Waveform</string>
 </resources>
diff --git a/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/CompositionSignalSamples.kt b/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/CompositionSignalSamples.kt
new file mode 100644
index 0000000..856b999
--- /dev/null
+++ b/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/CompositionSignalSamples.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.samples
+
+import androidx.annotation.Sampled
+import androidx.core.haptics.signal.CompositionSignal.Companion.compositionOf
+import androidx.core.haptics.signal.CompositionSignal.Companion.off
+import androidx.core.haptics.signal.CompositionSignal.Companion.quickFall
+import androidx.core.haptics.signal.CompositionSignal.Companion.slowRise
+import androidx.core.haptics.signal.CompositionSignal.Companion.thud
+
+/**
+ * Sample showing how to create a composition signal with scaled effects and off atoms.
+ */
+@Sampled
+fun CompositionSignalOfScaledEffectsAndOff() {
+    compositionOf(
+        slowRise().withAmplitudeScale(0.7f),
+        quickFall().withAmplitudeScale(0.7f),
+        off(durationMillis = 50),
+        thud(),
+    )
+}
diff --git a/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/HapticManagerSamples.kt b/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/HapticManagerSamples.kt
index e1d3df4..83ce76d 100644
--- a/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/HapticManagerSamples.kt
+++ b/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/HapticManagerSamples.kt
@@ -19,7 +19,12 @@
 import android.content.Context
 import androidx.annotation.Sampled
 import androidx.core.haptics.HapticManager
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedClick
+import androidx.core.haptics.signal.CompositionSignal.Companion.compositionOf
+import androidx.core.haptics.signal.CompositionSignal.Companion.off
+import androidx.core.haptics.signal.CompositionSignal.Companion.quickFall
+import androidx.core.haptics.signal.CompositionSignal.Companion.slowRise
+import androidx.core.haptics.signal.CompositionSignal.Companion.thud
+import androidx.core.haptics.signal.PredefinedEffectSignal.Companion.predefinedClick
 
 /**
  * Sample showing how to play a standard click haptic effect on the system vibrator.
@@ -27,5 +32,20 @@
 @Sampled
 fun PlaySystemStandardClick(context: Context) {
     val hapticManager = HapticManager.create(context)
-    hapticManager.play(PredefinedClick)
+    hapticManager.play(predefinedClick())
+}
+
+/**
+ * Sample showing how to play a haptic signal on a vibrator.
+ */
+@Sampled
+fun PlayHapticSignal(hapticManager: HapticManager) {
+    hapticManager.play(
+        compositionOf(
+            slowRise(),
+            quickFall(),
+            off(durationMillis = 50),
+            thud(),
+        )
+    )
 }
diff --git a/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/WaveformSignalSamples.kt b/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/WaveformSignalSamples.kt
new file mode 100644
index 0000000..3b7b597
--- /dev/null
+++ b/core/haptics/haptics/samples/src/main/java/androidx/core/haptics/samples/WaveformSignalSamples.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.samples
+
+import androidx.annotation.Sampled
+import androidx.core.haptics.signal.WaveformSignal
+import androidx.core.haptics.signal.WaveformSignal.Companion.off
+import androidx.core.haptics.signal.WaveformSignal.Companion.on
+import androidx.core.haptics.signal.WaveformSignal.Companion.repeatingWaveformOf
+import androidx.core.haptics.signal.WaveformSignal.Companion.waveformOf
+
+/**
+ * Sample showing how to create an on-off pattern.
+ */
+@Sampled
+fun PatternWaveform() {
+    waveformOf(
+        on(durationMillis = 250),
+        off(durationMillis = 350),
+        on(durationMillis = 250),
+    )
+}
+
+/**
+ * Sample showing how to create an infinite haptic signal a repeating step waveform.
+ */
+@Sampled
+fun PatternWaveformRepeat() {
+    waveformOf(
+        on(durationMillis = 100),
+        off(durationMillis = 50),
+        on(durationMillis = 100),
+        off(durationMillis = 50),
+    ).repeat()
+}
+
+/**
+ * Sample showing how to create an amplitude step waveform.
+ */
+@Sampled
+fun AmplitudeWaveform() {
+    waveformOf(
+        on(durationMillis = 10, amplitude = 0.2f),
+        on(durationMillis = 20, amplitude = 0.4f),
+        on(durationMillis = 30, amplitude = 0.8f),
+        on(durationMillis = 40, amplitude = 1f),
+        off(durationMillis = 50),
+        on(durationMillis = 50),
+    )
+}
+
+/**
+ * Sample showing how to create an amplitude step waveform.
+ */
+@Sampled
+fun RepeatingAmplitudeWaveform() {
+    repeatingWaveformOf(
+        on(durationMillis = 100),
+        off(durationMillis = 50),
+    )
+}
+
+/**
+ * Sample showing how to create an infinite haptic signal as repeating step waveform.
+ */
+@Sampled
+fun PatternThenRepeatExistingWaveform(waveformSignal: WaveformSignal) {
+    waveformOf(
+        on(durationMillis = 100),
+        off(durationMillis = 50),
+        on(durationMillis = 100),
+        off(durationMillis = 500),
+    ).thenRepeat(waveformSignal)
+}
+
+/**
+ * Sample showing how to create an infinite haptic signal as repeating step waveform.
+ */
+@Sampled
+fun PatternThenRepeatAmplitudeWaveform() {
+    waveformOf(
+        on(durationMillis = 100),
+        off(durationMillis = 50),
+        on(durationMillis = 100),
+    ).thenRepeat(
+        on(durationMillis = 500, amplitude = 0f),
+        on(durationMillis = 100, amplitude = 0.2f),
+        on(durationMillis = 200, amplitude = 0.4f),
+        on(durationMillis = 300, amplitude = 0.8f),
+        on(durationMillis = 400, amplitude = 1f),
+    )
+}
diff --git a/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayCompositionSignalTest.kt b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayCompositionSignalTest.kt
new file mode 100644
index 0000000..584ffc637
--- /dev/null
+++ b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayCompositionSignalTest.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics
+
+import android.os.Build
+import androidx.core.haptics.signal.CompositionSignal.Companion.click
+import androidx.core.haptics.signal.CompositionSignal.Companion.compositionOf
+import androidx.core.haptics.signal.CompositionSignal.Companion.lowTick
+import androidx.core.haptics.signal.CompositionSignal.Companion.off
+import androidx.core.haptics.signal.CompositionSignal.Companion.quickFall
+import androidx.core.haptics.signal.CompositionSignal.Companion.quickRise
+import androidx.core.haptics.signal.CompositionSignal.Companion.slowRise
+import androidx.core.haptics.signal.CompositionSignal.Companion.spin
+import androidx.core.haptics.signal.CompositionSignal.Companion.thud
+import androidx.core.haptics.signal.CompositionSignal.Companion.tick
+import androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom
+import androidx.core.haptics.testing.CompositionPrimitive
+import androidx.core.haptics.testing.FakeVibratorSubject.Companion.assertThat
+import androidx.core.haptics.testing.FullVibrator
+import androidx.core.haptics.testing.vibration
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import kotlin.time.Duration.Companion.milliseconds
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.Parameterized
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+@RunWith(Parameterized::class)
+@SmallTest
+class PlayCompositionSignalSdk30AndAboveTest(
+    private val primitive: PrimitiveAtom,
+) {
+    private val fakeVibrator = FullVibrator()
+    private val hapticManager = HapticManager.createForVibrator(fakeVibrator)
+
+    @Test
+    fun play_vibratesWithSupportedPrimitives() {
+        hapticManager.play(
+            compositionOf(
+                primitive,
+                off(durationMillis = 50),
+                primitive.withAmplitudeScale(0.5f),
+                off(durationMillis = 100),
+                primitive.withAmplitudeScale(0.8f),
+                off(durationMillis = 200),
+            )
+        )
+        assertThat(fakeVibrator).vibratedExactly(
+            vibration(
+                CompositionPrimitive(primitive),
+                CompositionPrimitive(primitive, scale = 0.5f, delay = 50.milliseconds),
+                CompositionPrimitive(primitive, scale = 0.8f, delay = 100.milliseconds),
+                // Skips trailing 200ms delay from vibrate call
+            )
+        )
+    }
+
+    companion object {
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "primitive:{0}")
+        fun data(): Collection<Any> {
+            val primitives = mutableListOf(
+                tick(),
+                click(),
+                slowRise(),
+                quickRise(),
+                quickFall(),
+            )
+            if (Build.VERSION.SDK_INT >= 31) {
+                primitives.apply {
+                    add(lowTick())
+                    add(spin())
+                    add(thud())
+                }
+            }
+            return primitives
+        }
+    }
+}
+
+@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.Q)
+@RunWith(Parameterized::class)
+@SmallTest
+class PlayCompositionSignalBelowSdk30Test(
+    private val primitive: PrimitiveAtom,
+) {
+    private val fakeVibrator = FullVibrator()
+    private val hapticManager = HapticManager.createForVibrator(fakeVibrator)
+
+    @Test
+    fun play_doesNotVibrate() {
+        hapticManager.play(compositionOf(primitive))
+        assertThat(fakeVibrator).neverVibrated()
+    }
+
+    companion object {
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "primitive:{0}")
+        fun data(): Collection<Any> = mutableListOf(
+            tick(),
+            click(),
+            slowRise(),
+            quickRise(),
+            quickFall(),
+            lowTick(),
+            spin(),
+            thud(),
+        )
+    }
+}
+
+@RunWith(JUnit4::class)
+@SmallTest
+class PlayCompositionSignalPartialPrimitiveSdkSupportTest {
+    private val fakeVibrator = FullVibrator()
+    private val hapticManager = HapticManager.createForVibrator(fakeVibrator)
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R, maxSdkVersion = Build.VERSION_CODES.R)
+    @Test
+    fun play_api30AndPrimitiveFromApi31AndAbove_doesNotVibrate() {
+        hapticManager.play(compositionOf(lowTick()))
+        hapticManager.play(compositionOf(thud()))
+        hapticManager.play(compositionOf(spin()))
+        // Mix supported/unsupported primitives
+        hapticManager.play(compositionOf(tick(), lowTick()))
+        assertThat(fakeVibrator).neverVibrated()
+    }
+}
+
+@RunWith(JUnit4::class)
+@SmallTest
+class PlayCompositionSignalAllSdksTest {
+
+    @Test
+    fun compositionOf_withNoAtom_throwsException() {
+        assertThrows(IllegalArgumentException::class.java) {
+            compositionOf()
+        }
+    }
+
+    @Test
+    fun off_withNegativeDuration_throwsException() {
+        assertThrows(IllegalArgumentException::class.java) {
+            off(durationMillis = -10)
+        }
+    }
+
+    @Test
+    fun withAmplitudeScale_withAmplitudeLargerThanOne_throwsException() {
+        assertThrows(IllegalArgumentException::class.java) {
+            click().withAmplitudeScale(2f)
+        }
+    }
+
+    @Test
+    fun withAmplitudeScale_withNegativeAmplitude_throwsException() {
+        assertThrows(IllegalArgumentException::class.java) {
+            click().withAmplitudeScale(-1f)
+        }
+    }
+}
diff --git a/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayPredefinedEffectSignalTest.kt b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayPredefinedEffectSignalTest.kt
new file mode 100644
index 0000000..f5572c1
--- /dev/null
+++ b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayPredefinedEffectSignalTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics
+
+import android.os.Build
+import androidx.core.haptics.signal.PredefinedEffectSignal
+import androidx.core.haptics.signal.PredefinedEffectSignal.Companion.predefinedClick
+import androidx.core.haptics.signal.PredefinedEffectSignal.Companion.predefinedDoubleClick
+import androidx.core.haptics.signal.PredefinedEffectSignal.Companion.predefinedHeavyClick
+import androidx.core.haptics.signal.PredefinedEffectSignal.Companion.predefinedTick
+import androidx.core.haptics.testing.FakeVibratorSubject.Companion.assertThat
+import androidx.core.haptics.testing.PredefinedEffectsAndAmplitudeVibrator
+import androidx.core.haptics.testing.vibration
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@SmallTest
+class PlayPredefinedEffectSignalTest(
+    private val effect: PredefinedEffectSignal,
+    private val expectedFallbackPattern: LongArray,
+) {
+    private val fakeVibrator = PredefinedEffectsAndAmplitudeVibrator()
+    private val hapticManager = HapticManager.createForVibrator(fakeVibrator)
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun play_api29AndAbove_vibratesWithPredefinedEffect() {
+        hapticManager.play(effect)
+        assertThat(fakeVibrator).vibratedExactly(vibration(effect))
+    }
+
+    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.P)
+    @Test
+    fun play_belowApi29_vibratesWithFallbackPattern() {
+        hapticManager.play(effect)
+        assertThat(fakeVibrator).vibratedExactly(vibration(expectedFallbackPattern))
+    }
+
+    companion object {
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "effect:{0}, expectedFallbackPattern:{1}")
+        fun data(): Collection<Array<Any>> = listOf(
+            arrayOf(predefinedTick(), longArrayOf(0, 10)),
+            arrayOf(predefinedClick(), longArrayOf(0, 20)),
+            arrayOf(predefinedHeavyClick(), longArrayOf(0, 30)),
+            arrayOf(predefinedDoubleClick(), longArrayOf(0, 30, 100, 30)),
+        )
+    }
+}
diff --git a/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayPredefinedEffectTest.kt b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayPredefinedEffectTest.kt
deleted file mode 100644
index fab916e..0000000
--- a/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayPredefinedEffectTest.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.core.haptics
-
-import android.os.Build
-import android.os.VibrationEffect
-import android.os.Vibrator
-import androidx.core.haptics.signal.PredefinedEffect
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedClick
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedDoubleClick
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedHeavyClick
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedTick
-import androidx.test.filters.SdkSuppress
-import androidx.test.filters.SmallTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-
-@RunWith(Parameterized::class)
-@SmallTest
-class PlayPredefinedEffectTest(
-    private val effect: PredefinedEffect,
-    private val expectedFallbackPattern: LongArray,
-) {
-    // Vibrator has package-protected constructor and cannot be extended by a FakeVibrator
-    // TODO(b/275084444): replace with a testable interface to allow all SDK levels
-    private val vibrator = mock(Vibrator::class.java)
-    private val hapticManager = HapticManager.createForVibrator(vibrator)
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
-    @Test
-    fun perform_api29AndAbove() {
-        hapticManager.play(effect)
-        verify(vibrator).vibrate(eq(VibrationEffect.createPredefined(effect.effectId)))
-    }
-
-    @Suppress("DEPRECATION") // Verifying deprecated APIs are triggered by this test
-    @SdkSuppress(
-        minSdkVersion = 28, // TODO(b/275084444): remove this once we introduce fake vibrator
-        maxSdkVersion = Build.VERSION_CODES.P
-    )
-    @Test
-    fun perform_belowApi29() {
-        hapticManager.play(effect)
-        verify(vibrator).vibrate(eq(expectedFallbackPattern), eq(-1))
-    }
-
-    companion object {
-
-        @JvmStatic
-        @Parameterized.Parameters(name = "effect:{0}, expectedFallbackPattern:{1}")
-        fun data(): Collection<Array<Any>> = listOf(
-            arrayOf(PredefinedTick, longArrayOf(0, 10)),
-            arrayOf(PredefinedClick, longArrayOf(0, 20)),
-            arrayOf(PredefinedHeavyClick, longArrayOf(0, 30)),
-            arrayOf(PredefinedDoubleClick, longArrayOf(0, 30, 100, 30)),
-        )
-    }
-}
diff --git a/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayWaveformSignalTest.kt b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayWaveformSignalTest.kt
new file mode 100644
index 0000000..cd7a70d
--- /dev/null
+++ b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/PlayWaveformSignalTest.kt
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics
+
+import android.os.Build
+import androidx.core.haptics.signal.WaveformSignal.Companion.off
+import androidx.core.haptics.signal.WaveformSignal.Companion.on
+import androidx.core.haptics.signal.WaveformSignal.Companion.repeatingWaveformOf
+import androidx.core.haptics.signal.WaveformSignal.Companion.waveformOf
+import androidx.core.haptics.testing.AmplitudeVibrator
+import androidx.core.haptics.testing.FakeVibratorSubject.Companion.assertThat
+import androidx.core.haptics.testing.PatternVibrator
+import androidx.core.haptics.testing.vibration
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@RunWith(JUnit4::class)
+@SmallTest
+class PlayWaveformSignalSdk26AndAboveTest {
+    private val fakeVibrator = AmplitudeVibrator()
+    private val hapticManager = HapticManager.createForVibrator(fakeVibrator)
+
+    @Test
+    fun play_withOneShot_vibratesWithOneShotEffect() {
+        hapticManager.play(waveformOf(on(durationMillis = 10)))
+        hapticManager.play(waveformOf(on(durationMillis = 20, amplitude = 0.2f)))
+        assertThat(fakeVibrator).vibratedExactly(
+            vibration(timings = longArrayOf(10), amplitudes = intArrayOf(-1)),
+            vibration(timings = longArrayOf(20), amplitudes = intArrayOf(51)),
+        ).inOrder()
+    }
+
+    @Test
+    fun play_withAmplitudes_vibratesWithAmplitudes() {
+        hapticManager.play(
+            waveformOf(
+                on(durationMillis = 10, amplitude = 0.2f),
+                on(durationMillis = 20, amplitude = 0.8f),
+                on(durationMillis = 30, amplitude = 0f),
+                on(durationMillis = 40, amplitude = 1f),
+            )
+        )
+        assertThat(fakeVibrator).vibratedExactly(
+            vibration(
+                timings = longArrayOf(10, 20, 30, 40),
+                amplitudes = intArrayOf(51, 204, 0, 255),
+            )
+        )
+    }
+
+    @Test
+    fun play_withOnOffPattern_vibratesWithAmplitudes() {
+        hapticManager.play(
+            waveformOf(
+                on(durationMillis = 10),
+                off(durationMillis = 20),
+                on(durationMillis = 30),
+                off(durationMillis = 40),
+            )
+        )
+        assertThat(fakeVibrator).vibratedExactly(
+            vibration(
+                timings = longArrayOf(10, 20, 30, 40),
+                amplitudes = intArrayOf(-1, 0, -1, 0),
+            )
+        )
+    }
+
+    @Test
+    fun play_withRepeatingAmplitudes_vibratesWithRepeatIndex() {
+        hapticManager.play(
+            waveformOf(
+                on(durationMillis = 10, amplitude = 0.2f),
+                on(durationMillis = 20, amplitude = 0.4f),
+            ).thenRepeat(
+                on(durationMillis = 30, amplitude = 0.6f),
+                on(durationMillis = 40, amplitude = 0.8f),
+            )
+        )
+        assertThat(fakeVibrator).vibratedExactly(
+            vibration(
+                timings = longArrayOf(10, 20, 30, 40),
+                amplitudes = intArrayOf(51, 102, 153, 204),
+                repeat = 2,
+            )
+        )
+    }
+}
+
+@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.N_MR1)
+@RunWith(JUnit4::class)
+@SmallTest
+class PlayWaveformSignalBelowSdk26Test {
+    private val fakeVibrator = PatternVibrator()
+    private val hapticManager = HapticManager.createForVibrator(fakeVibrator)
+
+    @Test
+    fun play_withOneShot_vibratesWithPatternForDefaultAndMaxAmplitudes() {
+        hapticManager.play(waveformOf(on(durationMillis = 10)))
+        hapticManager.play(waveformOf(on(durationMillis = 20, amplitude = 1f)))
+        hapticManager.play(waveformOf(on(durationMillis = 30, amplitude = 0.2f)))
+        assertThat(fakeVibrator).vibratedExactly(
+            vibration(pattern = longArrayOf(0, 10)),
+            vibration(pattern = longArrayOf(0, 20)),
+            // Ignores last request with non-default amplitude
+        ).inOrder()
+    }
+
+    @Test
+    fun play_withAmplitudes_doesNotVibrate() {
+        hapticManager.play(
+            waveformOf(
+                on(durationMillis = 10, amplitude = 0.2f),
+                on(durationMillis = 20, amplitude = 0.8f),
+                on(durationMillis = 30, amplitude = 0f),
+                on(durationMillis = 40, amplitude = 1f),
+            )
+        )
+        assertThat(fakeVibrator).neverVibrated()
+    }
+
+    @Test
+    fun play_withOnOffPattern_vibratesWithFallbackPattern() {
+        hapticManager.play(
+            waveformOf(
+                on(durationMillis = 10),
+                off(durationMillis = 20),
+                on(durationMillis = 30),
+                on(durationMillis = 40),
+                off(durationMillis = 50),
+                off(durationMillis = 60),
+            )
+        )
+        assertThat(fakeVibrator).vibratedExactly(
+            // OFF(0ms), ON(10ms), OFF(20ms), ON(30+40ms), OFF(50+60ms)
+            vibration(pattern = longArrayOf(0, 10, 20, 70, 110))
+        )
+    }
+
+    @Test
+    fun play_withOnOffMaxAmplitudePattern_vibratesWithFallbackPattern() {
+        hapticManager.play(
+            waveformOf(
+                on(durationMillis = 10),
+                on(durationMillis = 20, amplitude = 1f),
+                off(durationMillis = 30),
+                on(durationMillis = 40, amplitude = 0f),
+                on(durationMillis = 50),
+            )
+        )
+        assertThat(fakeVibrator).vibratedExactly(
+            // OFF(0ms), ON(10+20ms), OFF(30+40ms), ON(50ms)
+            vibration(pattern = longArrayOf(0, 30, 70, 50))
+        )
+    }
+
+    @Test
+    fun play_withRepeatingPattern_vibratesWithRepeatIndex() {
+        hapticManager.play(
+            repeatingWaveformOf(
+                off(durationMillis = 10),
+                on(durationMillis = 20),
+                off(durationMillis = 30),
+                off(durationMillis = 40),
+                on(durationMillis = 50),
+                on(durationMillis = 60),
+            )
+        )
+        assertThat(fakeVibrator).vibratedExactly(
+            vibration(
+                // OFF(10ms), ON(20ms), OFF(30+40ms), ON(50+60ms)
+                pattern = longArrayOf(10, 20, 70, 110),
+                repeat = 0,
+            )
+        )
+    }
+
+    @Test
+    fun play_withInitialAndRepeatingPattern_doesNotMergeInitialWithRepeatingPattern() {
+        hapticManager.play(
+            waveformOf(
+                on(durationMillis = 10),
+                off(durationMillis = 20),
+                off(durationMillis = 30),
+                on(durationMillis = 40),
+            ).thenRepeat(
+                on(durationMillis = 50),
+                on(durationMillis = 60),
+                off(durationMillis = 70),
+                off(durationMillis = 80),
+            )
+        )
+        assertThat(fakeVibrator).vibratedExactly(
+            vibration(
+                // Does not merge consecutive ON steps 40 and 50 because of repeat index.
+                // OFF(0ms), ON(10ms), OFF(20+30ms), ON(40ms), OFF(+0ms), ON(50+60ms), OFF(70+80ms)
+                pattern = longArrayOf(0, 10, 50, 40, 0, 110, 150),
+                repeat = 4,
+            )
+        )
+    }
+}
+
+@RunWith(JUnit4::class)
+@SmallTest
+class PlayWaveformSignalAllSdksTest {
+    private val fakeVibrator = AmplitudeVibrator()
+    private val hapticManager = HapticManager.createForVibrator(fakeVibrator)
+
+    @Test
+    fun play_withZeroDurationSignal_doesNotVibrate() {
+        hapticManager.play(waveformOf(on(durationMillis = 0)))
+        hapticManager.play(waveformOf(on(durationMillis = 0, amplitude = 0.2f)))
+        hapticManager.play(
+            waveformOf(
+                on(durationMillis = 0, amplitude = 0.2f),
+                on(durationMillis = 0, amplitude = 0.8f),
+                on(durationMillis = 0, amplitude = 0f),
+                on(durationMillis = 0, amplitude = 1f),
+                off(durationMillis = 0),
+            )
+        )
+        assertThat(fakeVibrator).neverVibrated()
+    }
+
+    @Test
+    fun waveformOf_withNoAtom_throwsException() {
+        assertThrows(IllegalArgumentException::class.java) {
+            waveformOf()
+        }
+    }
+
+    @Test
+    fun on_withAmplitudeLargerThanOne_throwsException() {
+        assertThrows(IllegalArgumentException::class.java) {
+            on(durationMillis = 0, amplitude = 1.1f)
+        }
+    }
+
+    @Test
+    fun on_withNegativeAmplitude_throwsException() {
+        assertThrows(IllegalArgumentException::class.java) {
+            on(durationMillis = 0, amplitude = -0.5f)
+        }
+    }
+
+    @Test
+    fun on_withNegativeDuration_throwsException() {
+        assertThrows(IllegalArgumentException::class.java) {
+            on(durationMillis = -10)
+        }
+    }
+
+    @Test
+    fun off_withNegativeDuration_throwsException() {
+        assertThrows(IllegalArgumentException::class.java) {
+            off(durationMillis = -10)
+        }
+    }
+}
diff --git a/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/testing/FakeVibrator.kt b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/testing/FakeVibrator.kt
new file mode 100644
index 0000000..2ff140a
--- /dev/null
+++ b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/testing/FakeVibrator.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.testing
+
+import android.os.Build
+import android.os.VibrationEffect
+import androidx.annotation.RequiresApi
+import androidx.core.haptics.PatternVibrationWrapper
+import androidx.core.haptics.VibrationEffectWrapper
+import androidx.core.haptics.VibrationWrapper
+import androidx.core.haptics.VibratorWrapper
+import androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom
+import androidx.core.haptics.signal.PredefinedEffectSignal
+import kotlin.time.Duration
+
+/**
+ * Fake [VibratorWrapper] implementation for testing.
+ */
+internal sealed class FakeVibrator(
+    private val amplitudeControlSupported: Boolean,
+    private val effectsSupported: IntArray? = null,
+    private val primitivesSupported: IntArray = intArrayOf(),
+) : VibratorWrapper {
+    private val vibrations: MutableList<VibrationWrapper> = mutableListOf()
+
+    override fun hasVibrator(): Boolean = true
+
+    override fun hasAmplitudeControl(): Boolean = amplitudeControlSupported
+
+    override fun areEffectsSupported(
+        effects: IntArray,
+    ): Array<VibratorWrapper.EffectSupport> =
+        effects.map {
+            when {
+                effectsSupported == null -> VibratorWrapper.EffectSupport.UNKNOWN
+                effectsSupported.contains(it) -> VibratorWrapper.EffectSupport.YES
+                else -> VibratorWrapper.EffectSupport.NO
+            }
+        }.toTypedArray()
+
+    override fun arePrimitivesSupported(primitives: IntArray): BooleanArray =
+        primitives.map { primitivesSupported.contains(it) }.toBooleanArray()
+
+    override fun vibrate(vibration: VibrationWrapper) {
+        vibrations.add(vibration)
+    }
+
+    override fun cancel() {
+        // No-op
+    }
+
+    /** Returns all requests sent to the [android.os.Vibrator], in order. */
+    internal fun vibrations(): List<VibrationWrapper> = vibrations
+}
+
+/**
+ * Vibrator that only supports on-off patterns.
+ */
+internal class PatternVibrator : FakeVibrator(
+    amplitudeControlSupported = false,
+)
+
+/**
+ * Vibrator that only supports amplitude control.
+ */
+internal class AmplitudeVibrator : FakeVibrator(
+    amplitudeControlSupported = true,
+)
+
+/**
+ * Vibrator that supports amplitude control and all predefined effects.
+ */
+internal class PredefinedEffectsAndAmplitudeVibrator : FakeVibrator(
+    amplitudeControlSupported = true,
+    effectsSupported = intArrayOf(
+        PredefinedEffectSignal.TICK,
+        PredefinedEffectSignal.CLICK,
+        PredefinedEffectSignal.HEAVY_CLICK,
+        PredefinedEffectSignal.DOUBLE_CLICK,
+    ),
+)
+
+/**
+ * Vibrator that supports amplitude control and all predefined and primitive effects.
+ */
+internal class FullVibrator : FakeVibrator(
+    amplitudeControlSupported = true,
+    effectsSupported = intArrayOf(
+        PredefinedEffectSignal.TICK,
+        PredefinedEffectSignal.CLICK,
+        PredefinedEffectSignal.HEAVY_CLICK,
+        PredefinedEffectSignal.DOUBLE_CLICK,
+    ),
+    primitivesSupported = intArrayOf(
+        PrimitiveAtom.LOW_TICK,
+        PrimitiveAtom.TICK,
+        PrimitiveAtom.CLICK,
+        PrimitiveAtom.SLOW_RISE,
+        PrimitiveAtom.QUICK_RISE,
+        PrimitiveAtom.QUICK_FALL,
+        PrimitiveAtom.SPIN,
+        PrimitiveAtom.THUD,
+    ),
+)
+
+/** Helper to create [android.os.VibrationEffect.Composition] entries. */
+internal data class CompositionPrimitive(
+    val primitiveId: Int,
+    val scale: Float,
+    val delayMs: Int,
+) {
+    constructor(
+        primitive: PrimitiveAtom,
+        scale: Float = primitive.amplitudeScale,
+        delay: Duration = Duration.ZERO,
+    ) : this(primitive.type, scale, delay.inWholeMilliseconds.toInt())
+}
+
+/** Helper to create [VibrationWrapper] request for a on-off pattern. */
+internal fun vibration(
+    pattern: LongArray,
+    repeat: Int = -1,
+): VibrationWrapper =
+    PatternVibrationWrapper(pattern, repeat)
+
+/** Helper to create [VibrationWrapper] request for a predefined effect. */
+@RequiresApi(Build.VERSION_CODES.Q)
+internal fun vibration(effect: PredefinedEffectSignal): VibrationWrapper =
+    VibrationEffectWrapper(VibrationEffect.createPredefined(effect.type))
+
+/** Helper to create [VibrationWrapper] request for a waveform effect. */
+@RequiresApi(Build.VERSION_CODES.O)
+internal fun vibration(
+    timings: LongArray,
+    amplitudes: IntArray,
+    repeat: Int = -1,
+): VibrationWrapper =
+    VibrationEffectWrapper(VibrationEffect.createWaveform(timings, amplitudes, repeat))
+
+/** Helper to create [VibrationWrapper] request for a primitive composition effect. */
+@RequiresApi(Build.VERSION_CODES.R)
+internal fun vibration(vararg primitives: CompositionPrimitive): VibrationWrapper {
+    return VibrationEffectWrapper(
+        VibrationEffect.startComposition().apply {
+            primitives.forEach { addPrimitive(it.primitiveId, it.scale, it.delayMs) }
+        }.compose()
+    )
+}
diff --git a/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/testing/FakeVibratorSubject.kt b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/testing/FakeVibratorSubject.kt
new file mode 100644
index 0000000..9ab2ca2
--- /dev/null
+++ b/core/haptics/haptics/src/androidTest/java/androidx/core/haptics/testing/FakeVibratorSubject.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.testing
+
+import androidx.core.haptics.VibrationWrapper
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Ordered
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth.assertAbout
+
+/**
+ * Truth extension for [FakeVibrator].
+ */
+internal class FakeVibratorSubject private constructor(
+    metadata: FailureMetadata?,
+    private val actual: FakeVibrator,
+) : Subject(metadata, actual) {
+
+    companion object {
+        private val SUBJECT_FACTORY: Factory<FakeVibratorSubject?, FakeVibrator> =
+            Factory { failureMetadata, subject -> FakeVibratorSubject(failureMetadata, subject) }
+
+        internal fun assertThat(vibrator: FakeVibrator): FakeVibratorSubject =
+            requireNotNull(assertAbout(SUBJECT_FACTORY).that(vibrator))
+    }
+
+    /**
+     * Checks the subject was requested to vibrate with exactly the provided parameters.
+     *
+     * To also test that the requests appear in the given order, make a call to inOrder() on the
+     * object returned by this method.
+     */
+    fun vibratedExactly(vararg expected: VibrationWrapper): Ordered =
+        check("vibrations()").that(actual.vibrations()).containsExactly(*expected)
+
+    /**
+     * Checks the subject has never requested to vibrate.
+     */
+    fun neverVibrated(): Unit =
+        check("vibrations()").that(actual.vibrations()).isEmpty()
+}
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/HapticManager.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/HapticManager.kt
index 1ca0bd0..37b74bd 100644
--- a/core/haptics/haptics/src/main/java/androidx/core/haptics/HapticManager.kt
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/HapticManager.kt
@@ -19,11 +19,14 @@
 import android.content.Context
 import android.os.Vibrator
 import androidx.annotation.RequiresPermission
+import androidx.annotation.VisibleForTesting
+import androidx.core.content.ContextCompat
 import androidx.core.haptics.impl.HapticManagerImpl
-import androidx.core.haptics.signal.PredefinedEffect
+import androidx.core.haptics.impl.VibratorWrapperImpl
+import androidx.core.haptics.signal.HapticSignal
 
 /**
- * Manager for the vibrators of a device.
+ * Manager for interactions with a device vibrator.
  *
  * <p>If your process exits, any vibration you started will stop.
  */
@@ -32,35 +35,38 @@
     companion object {
 
         /**
-         * Creates haptic manager for the system vibrators.
+         * Creates a haptic manager for the system vibrator.
          *
-         * Sample code:
          * @sample androidx.core.haptics.samples.PlaySystemStandardClick
          *
-         * @param context Context to load the device vibrators.
-         * @return a new instance of HapticManager for the system vibrators.
+         * @param context Context to load the device vibrator.
+         * @return a new instance of HapticManager for the system vibrator.
          */
         @JvmStatic
         fun create(context: Context): HapticManager {
-            return HapticManagerImpl(context)
+            return HapticManagerImpl(
+                VibratorWrapperImpl(
+                    requireNotNull(ContextCompat.getSystemService(context, Vibrator::class.java)) {
+                        "Vibrator service not found"
+                    }
+                )
+            )
         }
 
-        /** Creates haptic manager for given vibrator. */
-        internal fun createForVibrator(vibrator: Vibrator): HapticManager {
+        /** Creates a haptic manager for the given vibrator. */
+        @VisibleForTesting
+        internal fun createForVibrator(vibrator: VibratorWrapper): HapticManager {
             return HapticManagerImpl(vibrator)
         }
     }
 
     /**
-     * Play a [PredefinedEffect].
+     * Play a [HapticSignal].
      *
-     * The app should be in the foreground for the vibration to happen.
+     * @sample androidx.core.haptics.samples.PlayHapticSignal
      *
-     * Sample code:
-     * @sample androidx.core.haptics.samples.PlaySystemStandardClick
-     *
-     * @param effect The predefined haptic effect to be played.
+     * @param signal The haptic signal to be played.
      */
     @RequiresPermission(android.Manifest.permission.VIBRATE)
-    fun play(effect: PredefinedEffect)
+    fun play(signal: HapticSignal)
 }
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/VibratorWrapper.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/VibratorWrapper.kt
new file mode 100644
index 0000000..683c071
--- /dev/null
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/VibratorWrapper.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics
+
+import androidx.annotation.RequiresPermission
+
+/**
+ * Internal wrapper for [android.os.Vibrator] to enable fake implementations for testing.
+ */
+internal interface VibratorWrapper {
+    /** Check whether the hardware has a vibrator. */
+    fun hasVibrator(): Boolean
+
+    /** Check whether the vibrator has amplitude control. */
+    fun hasAmplitudeControl(): Boolean
+
+    /** Check whether the hardware supports each predefined effect type. */
+    fun areEffectsSupported(effects: IntArray): Array<EffectSupport>?
+
+    /** Check whether the hardware supports each primitive effect type. */
+    fun arePrimitivesSupported(primitives: IntArray): BooleanArray?
+
+    /** Vibrate with a given vibration effect or pattern. */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    fun vibrate(vibration: VibrationWrapper)
+
+    /** Cancel any ongoing vibration from this app and turns the vibrator off. */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    fun cancel()
+
+    /** Represents constants from [android.os.Vibrator.VIBRATION_EFFECT_SUPPORT_*]. */
+    enum class EffectSupport {
+        UNKNOWN, YES, NO
+    }
+}
+
+/**
+ * Represents different API levels of support for [android.os.Vibrator.vibrate] parameters.
+ */
+internal sealed interface VibrationWrapper
+
+/**
+ * Represents vibrations defined by on-off patterns.
+ */
+internal data class PatternVibrationWrapper(
+    val timings: LongArray,
+    val repeatIndex: Int,
+) : VibrationWrapper {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as PatternVibrationWrapper
+
+        if (!timings.contentEquals(other.timings)) return false
+        if (repeatIndex != other.repeatIndex) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = timings.contentHashCode()
+        result = 31 * result + repeatIndex.hashCode()
+        return result
+    }
+}
+
+/**
+ * Represents vibrations defined by an instance of [android.os.VibrationEffect].
+ */
+internal data class VibrationEffectWrapper(
+    val vibrationEffect: Any,
+) : VibrationWrapper
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/HapticManagerImpl.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/HapticManagerImpl.kt
index a5c518c..bd249c9 100644
--- a/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/HapticManagerImpl.kt
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/HapticManagerImpl.kt
@@ -16,72 +16,23 @@
 
 package androidx.core.haptics.impl
 
-import android.content.Context
-import android.os.Build.VERSION
-import android.os.VibrationEffect
 import android.os.Vibrator
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresApi
 import androidx.annotation.RequiresPermission
-import androidx.core.content.ContextCompat
 import androidx.core.haptics.HapticManager
-import androidx.core.haptics.signal.PredefinedEffect
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedClick
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedDoubleClick
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedHeavyClick
-import androidx.core.haptics.signal.PredefinedEffect.Companion.PredefinedTick
+import androidx.core.haptics.VibratorWrapper
+import androidx.core.haptics.signal.HapticSignal
 
 /**
  * [HapticManager] implementation for the [Vibrator] service.
  */
 internal class HapticManagerImpl internal constructor(
-    private val vibrator: Vibrator
+    private val vibrator: VibratorWrapper
 ) : HapticManager {
 
-    internal constructor(context: Context) : this(
-        requireNotNull(ContextCompat.getSystemService(context, Vibrator::class.java)) {
-            "Vibrator service not found"
-        }
-    )
-
     @RequiresPermission(android.Manifest.permission.VIBRATE)
-    override fun play(effect: PredefinedEffect) {
-        if (VERSION.SDK_INT >= 29) {
-            Api29Impl.play(vibrator, effect)
-        } else {
-            ApiImpl.play(vibrator, effect)
-        }
-    }
-
-    /** Version-specific static inner class. */
-    @RequiresApi(29)
-    private object Api29Impl {
-
-        @JvmStatic
-        @DoNotInline
-        @RequiresPermission(android.Manifest.permission.VIBRATE)
-        fun play(vibrator: Vibrator, effect: PredefinedEffect) {
-            vibrator.vibrate(VibrationEffect.createPredefined(effect.effectId))
-        }
-    }
-
-    /** Version-specific static inner class. */
-    private object ApiImpl {
-
-        private val predefinedEffectFallbackPatterns = mapOf(
-            PredefinedTick to longArrayOf(0, 10),
-            PredefinedClick to longArrayOf(0, 20),
-            PredefinedHeavyClick to longArrayOf(0, 30),
-            PredefinedDoubleClick to longArrayOf(0, 30, 100, 30)
-        )
-
-        @JvmStatic
-        @Suppress("DEPRECATION") // ApkVariant for compatibility
-        @RequiresPermission(android.Manifest.permission.VIBRATE)
-        fun play(vibrator: Vibrator, effect: PredefinedEffect) {
-            predefinedEffectFallbackPatterns[effect]?.let {
-                vibrator.vibrate(/* pattern= */ it, /* repeat= */ -1)
-            }
+    override fun play(signal: HapticSignal) {
+        signal.toVibration()?.let {
+                vibration -> vibrator.vibrate(vibration)
         }
     }
 }
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/HapticSignalConverter.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/HapticSignalConverter.kt
new file mode 100644
index 0000000..705c6d1
--- /dev/null
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/HapticSignalConverter.kt
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.impl
+
+import android.os.Build
+import android.os.VibrationEffect
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.core.haptics.PatternVibrationWrapper
+import androidx.core.haptics.VibrationEffectWrapper
+import androidx.core.haptics.VibrationWrapper
+import androidx.core.haptics.signal.CompositionSignal
+import androidx.core.haptics.signal.CompositionSignal.OffAtom
+import androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom
+import androidx.core.haptics.signal.PredefinedEffectSignal
+import androidx.core.haptics.signal.WaveformSignal
+import androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom
+import androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom.Companion.DEFAULT_AMPLITUDE
+import kotlin.math.roundToInt
+
+private const val VIBRATION_DEFAULT_AMPLITUDE: Int = -1 // VibrationEffect.DEFAULT_AMPLITUDE
+private const val VIBRATION_MAX_AMPLITUDE: Int = 255 // VibrationEffect.MAX_AMPLITUDE
+
+/** Returns true if amplitude is 0, 1 or [DEFAULT_AMPLITUDE]. */
+internal fun ConstantVibrationAtom.hasPatternAmplitude(): Boolean =
+    (amplitude == 0f) || (amplitude == 1f) || (amplitude == DEFAULT_AMPLITUDE)
+
+/** Returns the amplitude value in [0,255] or [android.os.VibrationEffect.DEFAULT_AMPLITUDE]. */
+internal fun ConstantVibrationAtom.getAmplitudeInt(): Int =
+    if (amplitude == DEFAULT_AMPLITUDE) {
+        VIBRATION_DEFAULT_AMPLITUDE
+    } else {
+        (amplitude * VIBRATION_MAX_AMPLITUDE).roundToInt()
+    }
+
+/**
+ * Helper class to convert haptic signals to platform types based on SDK support available.
+ */
+internal object HapticSignalConverter {
+
+    internal fun toVibration(effect: PredefinedEffectSignal): VibrationWrapper? =
+        if (Build.VERSION.SDK_INT >= 29) {
+            Api29Impl.toVibrationEffect(effect)
+        } else {
+            ApiImpl.toPatternVibration(effect)
+        }
+
+    internal fun toVibration(
+        initialWaveform: WaveformSignal?,
+        repeatingWaveform: WaveformSignal?
+    ): VibrationWrapper? =
+        if (Build.VERSION.SDK_INT >= 26) {
+            Api26Impl.toVibrationEffect(initialWaveform, repeatingWaveform)
+        } else {
+            ApiImpl.toPatternVibration(initialWaveform, repeatingWaveform)
+        }
+
+    internal fun toVibration(composition: CompositionSignal): VibrationWrapper? =
+        if (Build.VERSION.SDK_INT >= 31) {
+            Api31Impl.toVibrationEffect(composition)
+        } else if (Build.VERSION.SDK_INT >= 30) {
+            Api30Impl.toVibrationEffect(composition)
+        } else {
+            null
+        }
+
+    /** Version-specific static inner class. */
+    @RequiresApi(31)
+    private object Api31Impl {
+        @JvmStatic
+        @DoNotInline
+        fun toVibrationEffect(composition: CompositionSignal): VibrationEffectWrapper? =
+            if (composition.minSdk() <= 31) {
+                // Use same API to create composition from API 30, but allow constants from API 31.
+                Api30Impl.createComposition(composition)
+            } else {
+                null
+            }
+    }
+
+    /** Version-specific static inner class. */
+    @RequiresApi(30)
+    private object Api30Impl {
+        @JvmStatic
+        @DoNotInline
+        fun toVibrationEffect(composition: CompositionSignal): VibrationEffectWrapper? =
+            if (composition.minSdk() <= 30) {
+                createComposition(composition)
+            } else {
+                null
+            }
+
+        @JvmStatic
+        @DoNotInline
+        fun createComposition(composition: CompositionSignal): VibrationEffectWrapper? {
+            val platformComposition = VibrationEffect.startComposition()
+            var delayMs = 0
+
+            composition.atoms.forEach { atom ->
+                when (atom) {
+                    is PrimitiveAtom -> {
+                        platformComposition.addPrimitive(atom.type, atom.amplitudeScale, delayMs)
+                        delayMs = 0
+                    }
+
+                    is OffAtom -> {
+                        delayMs += atom.durationMillis.toInt()
+                    }
+
+                    else -> {
+                        // Unsupported composition atom
+                        return@createComposition null
+                    }
+                }
+            }
+
+            return VibrationEffectWrapper(platformComposition.compose())
+        }
+    }
+
+    /** Version-specific static inner class. */
+    @RequiresApi(29)
+    private object Api29Impl {
+        @JvmStatic
+        @DoNotInline
+        fun toVibrationEffect(effect: PredefinedEffectSignal): VibrationEffectWrapper? =
+            if (effect.minSdk() <= 29) {
+                VibrationEffectWrapper(VibrationEffect.createPredefined(effect.type))
+            } else {
+                null
+            }
+    }
+
+    /** Version-specific static inner class. */
+    @RequiresApi(26)
+    private object Api26Impl {
+
+        @JvmStatic
+        @DoNotInline
+        fun toVibrationEffect(
+            initialWaveform: WaveformSignal? = null,
+            repeatingWaveform: WaveformSignal? = null,
+        ): VibrationEffectWrapper? {
+            if (initialWaveform?.atoms?.any { it !is ConstantVibrationAtom } == true ||
+                repeatingWaveform?.atoms?.any { it !is ConstantVibrationAtom } == true) {
+                // Unsupported waveform atoms
+                return null
+            }
+
+            val initialAtoms =
+                initialWaveform?.atoms?.filterIsInstance<ConstantVibrationAtom>().orEmpty()
+            val repeatingAtoms =
+                repeatingWaveform?.atoms?.filterIsInstance<ConstantVibrationAtom>().orEmpty()
+            val allAtoms = initialAtoms + repeatingAtoms
+
+            val timings = allAtoms.map { it.durationMillis }.toLongArray()
+            val amplitudes = allAtoms.map { it.getAmplitudeInt() }.toIntArray()
+            val repeatIndex = if (repeatingAtoms.isNotEmpty()) initialAtoms.size else -1
+
+            if (timings.isEmpty() || timings.sum() == 0L) {
+                // Empty or zero duration waveforms not supported by VibrationEffect.createWaveform
+                return null
+            }
+
+            return VibrationEffectWrapper(
+                VibrationEffect.createWaveform(timings, amplitudes, repeatIndex)
+            )
+        }
+    }
+
+    /** Version-specific static inner class. */
+    private object ApiImpl {
+
+        @JvmStatic
+        fun toPatternVibration(effect: PredefinedEffectSignal): PatternVibrationWrapper? =
+            // Fallback patterns for predefined effects in SDK < 29.
+            when (effect.type) {
+                PredefinedEffectSignal.TICK ->
+                    PatternVibrationWrapper(longArrayOf(0, 10), repeatIndex = -1)
+                PredefinedEffectSignal.CLICK ->
+                    PatternVibrationWrapper(longArrayOf(0, 20), repeatIndex = -1)
+                PredefinedEffectSignal.HEAVY_CLICK ->
+                    PatternVibrationWrapper(longArrayOf(0, 30), repeatIndex = -1)
+                PredefinedEffectSignal.DOUBLE_CLICK ->
+                    PatternVibrationWrapper(longArrayOf(0, 30, 100, 30), repeatIndex = -1)
+                else ->
+                    null
+            }
+
+        @JvmStatic
+        fun toPatternVibration(
+            initialWaveform: WaveformSignal? = null,
+            repeatingWaveform: WaveformSignal? = null,
+        ): PatternVibrationWrapper? {
+            if (initialWaveform?.atoms?.any { it !is ConstantVibrationAtom } == true ||
+                repeatingWaveform?.atoms?.any { it !is ConstantVibrationAtom } == true) {
+                // Unsupported waveform entries
+                return null
+            }
+
+            val initialAtoms =
+                initialWaveform?.atoms?.filterIsInstance<ConstantVibrationAtom>().orEmpty()
+                    .toMutableList()
+            val repeatingAtoms =
+                repeatingWaveform?.atoms?.filterIsInstance<ConstantVibrationAtom>().orEmpty()
+
+            if (!initialAtoms.all { it.hasPatternAmplitude() } ||
+                !repeatingAtoms.all { it.hasPatternAmplitude() }) {
+                // Not possible to represent all amplitudes by an on-off pattern.
+                return null
+            }
+
+            val allAtoms = initialAtoms + repeatingAtoms
+            val timings = mutableListOf<Long>()
+            var currentIsOff = true // Vibration pattern starts with an off entry.
+            var currentTiming = 0L
+            var repeatIndex = -1
+
+            for ((index, atom) in allAtoms.withIndex()) {
+                if (index == initialAtoms.size) { // This is the first repeating atom.
+                    if (currentTiming > 0) {
+                        // Make sure not to merge the last initial atom to the first repeating one.
+                        timings.add(currentTiming)
+                        currentTiming = 0
+                        currentIsOff = !currentIsOff
+                    }
+                    // Mark the start of the repetition before adding the first repeating atom.
+                    repeatIndex = timings.size
+                }
+                val atomIsOff = atom.amplitude == 0f
+                if (currentIsOff == atomIsOff) {
+                    // Merge timings of same on/off state.
+                    currentTiming += atom.durationMillis
+                } else {
+                    // Start new timing with different on/off state.
+                    timings.add(currentTiming)
+                    currentTiming = atom.durationMillis
+                    currentIsOff = atomIsOff
+                }
+            }
+
+            if (currentTiming > 0) {
+                // Add last timing entry to the pattern.
+                timings.add(currentTiming)
+            }
+
+            if (timings.isEmpty() || timings.sum() == 0L) {
+                // Empty or zero duration waveforms are not supported by pattern vibrations.
+                return null
+            }
+
+            return PatternVibrationWrapper(timings.toLongArray(), repeatIndex)
+        }
+    }
+}
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/VibratorWrapperImpl.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/VibratorWrapperImpl.kt
new file mode 100644
index 0000000..decc9e0
--- /dev/null
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/impl/VibratorWrapperImpl.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.impl
+
+import android.annotation.SuppressLint
+import android.os.Build
+import android.os.VibrationEffect
+import android.os.Vibrator
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.annotation.RequiresPermission
+import androidx.core.haptics.PatternVibrationWrapper
+import androidx.core.haptics.VibrationEffectWrapper
+import androidx.core.haptics.VibrationWrapper
+import androidx.core.haptics.VibratorWrapper
+
+/**
+ * [VibratorWrapper] implementation backed by a real [Vibrator] service.
+ */
+internal class VibratorWrapperImpl(
+    private val vibrator: Vibrator
+) : VibratorWrapper {
+
+    override fun hasVibrator(): Boolean = ApiImpl.hasVibrator(vibrator)
+
+    override fun hasAmplitudeControl(): Boolean =
+        if (Build.VERSION.SDK_INT >= 26) {
+            Api26Impl.hasAmplitudeControl(vibrator)
+        } else {
+            false
+        }
+
+    override fun areEffectsSupported(effects: IntArray): Array<VibratorWrapper.EffectSupport>? =
+        if (Build.VERSION.SDK_INT >= 30) {
+            Api30Impl.areEffectsSupported(vibrator, effects)
+        } else {
+            null
+        }
+
+    override fun arePrimitivesSupported(primitives: IntArray): BooleanArray? =
+        if (Build.VERSION.SDK_INT >= 30) {
+            Api30Impl.arePrimitivesSupported(vibrator, primitives)
+        } else {
+            null
+        }
+
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    override fun vibrate(vibration: VibrationWrapper) {
+        when (vibration) {
+            is VibrationEffectWrapper -> {
+                check(Build.VERSION.SDK_INT >= 26) {
+                    "Attempting to vibrate with VibrationEffect before Android O is not supported"
+                }
+                if (Build.VERSION.SDK_INT >= 26) {
+                    Api26Impl.vibrate(vibrator, vibration)
+                }
+            }
+            is PatternVibrationWrapper ->
+                ApiImpl.vibrate(vibrator, vibration)
+        }
+    }
+
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    override fun cancel() {
+        ApiImpl.cancel(vibrator)
+    }
+
+    /** Version-specific static inner class. */
+    @RequiresApi(30)
+    private object Api30Impl {
+
+        @SuppressLint("WrongConstant") // custom conversion between jetpack and framework
+        @JvmStatic
+        @DoNotInline
+        fun areEffectsSupported(
+            vibrator: Vibrator,
+            effects: IntArray,
+        ): Array<VibratorWrapper.EffectSupport> {
+            return vibrator.areEffectsSupported(*effects).map {
+                when (it) {
+                    Vibrator.VIBRATION_EFFECT_SUPPORT_YES -> VibratorWrapper.EffectSupport.YES
+                    Vibrator.VIBRATION_EFFECT_SUPPORT_NO -> VibratorWrapper.EffectSupport.NO
+                    else -> VibratorWrapper.EffectSupport.UNKNOWN
+                }
+            }.toTypedArray()
+        }
+
+        @SuppressLint("WrongConstant") // custom conversion between jetpack and framework
+        @JvmStatic
+        @DoNotInline
+        fun arePrimitivesSupported(
+            vibrator: Vibrator,
+            primitives: IntArray,
+        ): BooleanArray {
+            return vibrator.arePrimitivesSupported(*primitives).toTypedArray().toBooleanArray()
+        }
+    }
+
+    /** Version-specific static inner class. */
+    @RequiresApi(26)
+    private object Api26Impl {
+
+        @JvmStatic
+        @DoNotInline
+        fun hasAmplitudeControl(vibrator: Vibrator) = vibrator.hasAmplitudeControl()
+
+        @JvmStatic
+        @DoNotInline
+        @RequiresPermission(android.Manifest.permission.VIBRATE)
+        fun vibrate(vibrator: Vibrator, effect: VibrationEffectWrapper) {
+            check(effect.vibrationEffect is VibrationEffect) {
+                "Attempting to vibrate with unexpected vibration effect ${effect.vibrationEffect}"
+            }
+            vibrator.vibrate(effect.vibrationEffect)
+        }
+    }
+
+    /** Version-specific static inner class. */
+    private object ApiImpl {
+
+        @JvmStatic
+        fun hasVibrator(vibrator: Vibrator) = vibrator.hasVibrator()
+
+        @JvmStatic
+        @Suppress("DEPRECATION") // ApkVariant for compatibility
+        @RequiresPermission(android.Manifest.permission.VIBRATE)
+        fun vibrate(vibrator: Vibrator, pattern: PatternVibrationWrapper) =
+            vibrator.vibrate(pattern.timings, pattern.repeatIndex)
+
+        @JvmStatic
+        @RequiresPermission(android.Manifest.permission.VIBRATE)
+        fun cancel(vibrator: Vibrator) = vibrator.cancel()
+    }
+}
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/CompositionSignal.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/CompositionSignal.kt
new file mode 100644
index 0000000..0197b93
--- /dev/null
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/CompositionSignal.kt
@@ -0,0 +1,444 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.signal
+
+import android.os.Build
+import androidx.annotation.FloatRange
+import androidx.annotation.IntDef
+import androidx.annotation.RequiresApi
+import androidx.annotation.RestrictTo
+import androidx.core.haptics.VibrationWrapper
+import androidx.core.haptics.impl.HapticSignalConverter
+import java.util.Objects
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.toKotlinDuration
+
+/**
+ * A composition of haptic elements that can be played as one single haptic effect.
+ *
+ * Composition signals may be defined as a composition of scalable primitive effects, which are
+ * tailored to the device hardware. The composition signal is based on the
+ * [android.os.VibrationEffect.Composition] platform API.
+ *
+ * @sample androidx.core.haptics.samples.CompositionSignalOfScaledEffectsAndOff
+ */
+class CompositionSignal(
+
+    /**
+     * The composition signal atoms that describes the haptic elements to be played in sequence.
+     */
+    val atoms: List<Atom>,
+
+) : FiniteSignal() {
+    init {
+        require(atoms.isNotEmpty()) { "Haptic signals cannot be empty" }
+    }
+
+    companion object {
+
+        /**
+         * Returns a [CompositionSignal] with given atoms.
+         *
+         * @sample androidx.core.haptics.samples.CompositionSignalOfScaledEffectsAndOff
+         *
+         * @param atoms The [CompositionSignal.Atom] instances that define the [CompositionSignal].
+         */
+        @JvmStatic
+        fun compositionOf(vararg atoms: Atom): CompositionSignal =
+            CompositionSignal(atoms.toList())
+
+        /**
+         * Returns a [CompositionSignal.Atom] for a very short low frequency tick effect.
+         *
+         * This effect should produce a light crisp sensation intended to be used repetitively
+         * for dynamic feedback.
+         *
+         * @param amplitudeScale The amplitude scale for the new [PrimitiveAtom]
+         */
+        @JvmOverloads
+        @JvmStatic
+        fun lowTick(@FloatRange(from = 0.0, to = 1.0) amplitudeScale: Float = 1f) =
+            PrimitiveAtom.LowTick.withAmplitudeScale(amplitudeScale)
+
+        /**
+         * Returns a [CompositionSignal.Atom] for a very short light tick effect.
+         *
+         * This effect should produce a light crisp sensation stronger than the [lowTick()], and is
+         * also intended to be used repetitively for dynamic feedback.
+         *
+         * @param amplitudeScale The amplitude scale for the new [PrimitiveAtom]
+         */
+        @JvmOverloads
+        @JvmStatic
+        fun tick(@FloatRange(from = 0.0, to = 1.0) amplitudeScale: Float = 1f) =
+            PrimitiveAtom.Tick.withAmplitudeScale(amplitudeScale)
+
+        /**
+         * Returns a [CompositionSignal.Atom] for a click effect.
+         *
+         * This effect should produce a sharp, crisp click sensation.
+         *
+         * @param amplitudeScale The amplitude scale for the new [PrimitiveAtom]
+         */
+        @JvmOverloads
+        @JvmStatic
+        fun click(@FloatRange(from = 0.0, to = 1.0) amplitudeScale: Float = 1f) =
+            PrimitiveAtom.Click.withAmplitudeScale(amplitudeScale)
+
+        /**
+         * Returns a [CompositionSignal.Atom] for an effect with increasing strength.
+         *
+         * This effect simulates quick upward movement against gravity.
+         *
+         * @param amplitudeScale The amplitude scale for the new [PrimitiveAtom]
+         */
+        @JvmOverloads
+        @JvmStatic
+        fun quickRise(@FloatRange(from = 0.0, to = 1.0) amplitudeScale: Float = 1f) =
+            PrimitiveAtom.QuickRise.withAmplitudeScale(amplitudeScale)
+
+        /**
+         * Returns a [CompositionSignal.Atom] for a longer effect with increasing strength.
+         *
+         * This effect simulates slow upward movement against gravity and is longer than the
+         * [quickRise()].
+         *
+         * @param amplitudeScale The amplitude scale for the new [PrimitiveAtom]
+         */
+        @JvmOverloads
+        @JvmStatic
+        fun slowRise(@FloatRange(from = 0.0, to = 1.0) amplitudeScale: Float = 1f) =
+            PrimitiveAtom.SlowRise.withAmplitudeScale(amplitudeScale)
+
+        /**
+         * Returns a [CompositionSignal.Atom] for an effect with decreasing strength.
+         *
+         * This effect simulates quick downwards movement against gravity.
+         *
+         * @param amplitudeScale The amplitude scale for the new [PrimitiveAtom]
+         */
+        @JvmOverloads
+        @JvmStatic
+        fun quickFall(@FloatRange(from = 0.0, to = 1.0) amplitudeScale: Float = 1f) =
+            PrimitiveAtom.QuickFall.withAmplitudeScale(amplitudeScale)
+
+        /**
+         * Returns a [CompositionSignal.Atom] for a spin effect.
+         *
+         * This effect simulates spinning momentum.
+         *
+         * @param amplitudeScale The amplitude scale for the new [PrimitiveAtom]
+         */
+        @JvmOverloads
+        @JvmStatic
+        fun spin(@FloatRange(from = 0.0, to = 1.0) amplitudeScale: Float = 1f) =
+            PrimitiveAtom.Spin.withAmplitudeScale(amplitudeScale)
+
+        /**
+         * Returns a [CompositionSignal.Atom] for a thud effect.
+         *
+         * This effect simulates downwards movement with gravity, often followed by extra energy
+         * of hitting and reverberation to augment physicality.
+         *
+         * @param amplitudeScale The amplitude scale for the new [PrimitiveAtom]
+         */
+        @JvmOverloads
+        @JvmStatic
+        fun thud(
+            @FloatRange(from = 0.0, to = 1.0) amplitudeScale: Float = 1f,
+        ) =
+            PrimitiveAtom.Thud.withAmplitudeScale(amplitudeScale)
+
+        /**
+         * Returns a [CompositionSignal.Atom] to turn the vibrator off for the specified duration.
+         *
+         * @sample androidx.core.haptics.samples.CompositionSignalOfScaledEffectsAndOff
+         *
+         * @param duration The duration the vibrator should be turned off.
+         */
+        @RequiresApi(Build.VERSION_CODES.O)
+        @JvmStatic
+        fun off(duration: java.time.Duration) =
+            OffAtom(duration.toKotlinDuration())
+
+        /**
+         * Returns a [CompositionSignal.Atom] to turn the vibrator off for the specified duration.
+         *
+         * @sample androidx.core.haptics.samples.CompositionSignalOfScaledEffectsAndOff
+         *
+         * @param durationMillis The duration the vibrator should be turned off, in milliseconds.
+         */
+        @JvmStatic
+        fun off(durationMillis: Long) =
+            OffAtom(durationMillis.milliseconds)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is CompositionSignal) return false
+        if (atoms != other.atoms) return false
+        return true
+    }
+
+    override fun hashCode(): Int {
+        return atoms.hashCode()
+    }
+
+    override fun toString(): String {
+        return "CompositionSignal(${atoms.joinToString()})"
+    }
+
+    /**
+     * Returns the minimum SDK level required by the atoms of this signal.
+     */
+    internal fun minSdk(): Int = atoms.maxOf { it.minSdk() }
+
+    override fun toVibration(): VibrationWrapper? = HapticSignalConverter.toVibration(this)
+
+    /**
+     * A [CompositionSignal.Atom] is a building block for creating a [CompositionSignal].
+     *
+     * Composition signal atoms describe basic haptic elements to be played in sequence as a single
+     * haptic effect. They can describe haptic effects tailored to the device hardware, like click
+     * and tick effects, or then can represent pauses in the effect composition.
+     *
+     * @sample androidx.core.haptics.samples.CompositionSignalOfScaledEffectsAndOff
+     */
+    abstract class Atom internal constructor() {
+
+        /**
+         * The minimum SDK level where this atom is available in the platform.
+         */
+        internal abstract fun minSdk(): Int
+    }
+
+    /**
+     * A [PrimitiveAtom] plays a haptic effect with the specified vibration strength scale.
+     *
+     * Composition primitives are haptic effects tailored to the device hardware with configurable
+     * vibration strength. They can be used as building blocks to create more complex haptic
+     * effects. The primitive atoms are based on the
+     * [android.os.VibrationEffect.Composition.addPrimitive] platform API.
+     *
+     * A primitive effect will always be played with a non-zero vibration amplitude, but the actual
+     * vibration strength can be scaled by values in the range [0f..1f]. Zero [amplitudeScale]
+     * implies the vibrator will play it at the minimum strength required for the effect to be
+     * perceived on the device. The maximum [amplitudeScale] value of 1 implies the vibrator will
+     * play it at the maximum strength that preserves the effect's intended design. For instance, a
+     * [Click] effect with [amplitudeScale] of 1 will usually feel stronger than a [Tick] with same
+     * amplitude scale.
+     *
+     * @sample androidx.core.haptics.samples.CompositionSignalOfScaledEffectsAndOff
+     */
+    class PrimitiveAtom private constructor(
+
+        /**
+         * The type of haptic effect to be played.
+         */
+        @Type val type: Int,
+
+        /**
+         * The minimum SDK level where this effect type is available in the platform.
+         */
+        private val minSdk: Int,
+
+        /**
+         * The scale for the vibration strength.
+         *
+         * A primitive effect will always be played with a non-zero vibration strength. Zero values
+         * here represent minimum effect strength that can still be perceived on the device, and
+         * maximum values represent the maximum strength the effect can be played.
+         */
+        @FloatRange(from = 0.0, to = 1.0) val amplitudeScale: Float = 1f,
+
+    ) : Atom() {
+        init {
+            require(amplitudeScale in 0.0..1.0) {
+                "Primitive amplitude scale must be in [0,1]: $amplitudeScale"
+            }
+        }
+
+        /** Typedef for the [type] attribute. */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        @Retention(AnnotationRetention.SOURCE)
+        @IntDef(
+            LOW_TICK,
+            TICK,
+            CLICK,
+            SLOW_RISE,
+            QUICK_RISE,
+            QUICK_FALL,
+            SPIN,
+            THUD,
+        )
+        annotation class Type
+
+        companion object {
+
+            /**
+             * A very short low frequency tick effect.
+             *
+             * This effect should produce a light crisp sensation intended to be used repetitively
+             * for dynamic feedback.
+             */
+            const val LOW_TICK = 8 // VibrationEffect.Composition.PRIMITIVE_LOW_TICK
+
+            /**
+             * A very short light tick effect.
+             *
+             * This effect should produce a light crisp sensation stronger than the [LowTick], and is
+             * also intended to be used repetitively for dynamic feedback.
+             */
+            const val TICK = 7 // VibrationEffect.Composition.PRIMITIVE_TICK
+
+            /**
+             * A click effect.
+             *
+             * This effect should produce a sharp, crisp click sensation.
+             */
+            const val CLICK = 1 // VibrationEffect.Composition.PRIMITIVE_CLICK
+
+            /**
+             * An effect with increasing strength.
+             *
+             * This effect simulates quick upward movement against gravity.
+             */
+            const val QUICK_RISE = 4 // VibrationEffect.Composition.PRIMITIVE_QUICK_RISE
+
+            /**
+             * A longer effect with increasing strength.
+             *
+             * This effect simulates slow upward movement against gravity and is longer than the
+             * [QuickRise].
+             */
+            const val SLOW_RISE = 5 // VibrationEffect.Composition.PRIMITIVE_SLOW_RISE
+
+            /**
+             * An effect with decreasing strength.
+             *
+             * This effect simulates quick downwards movement against gravity.
+             */
+            const val QUICK_FALL = 6 // VibrationEffect.Composition.PRIMITIVE_QUICK_FALL
+
+            /**
+             * A spin effect.
+             *
+             * This effect simulates spinning momentum.
+             */
+            const val SPIN = 3 // VibrationEffect.Composition.PRIMITIVE_SPIN
+
+            /**
+             * A thud effect.
+             *
+             * This effect simulates downwards movement with gravity, often followed by extra energy
+             * of hitting and reverberation to augment physicality.
+             */
+            const val THUD = 2 // VibrationEffect.Composition.PRIMITIVE_THUD
+
+            internal val LowTick = PrimitiveAtom(LOW_TICK, Build.VERSION_CODES.S)
+            internal val Tick = PrimitiveAtom(TICK, Build.VERSION_CODES.R)
+            internal val Click = PrimitiveAtom(CLICK, Build.VERSION_CODES.R)
+            internal val QuickRise = PrimitiveAtom(QUICK_RISE, Build.VERSION_CODES.R)
+            internal val SlowRise = PrimitiveAtom(SLOW_RISE, Build.VERSION_CODES.R)
+            internal val QuickFall = PrimitiveAtom(QUICK_FALL, Build.VERSION_CODES.R)
+            internal val Spin = PrimitiveAtom(SPIN, Build.VERSION_CODES.S)
+            internal val Thud = PrimitiveAtom(THUD, Build.VERSION_CODES.S)
+        }
+
+        /**
+         * Returns a [PrimitiveAtom] with same effect type and new [amplitudeScale].
+         *
+         * @sample androidx.core.haptics.samples.CompositionSignalOfScaledEffectsAndOff
+         *
+         * @param newAmplitudeScale The amplitude scale for the new [PrimitiveAtom]
+         * @return A new [PrimitiveAtom] with the same effect type and the new amplitude scale.
+         */
+        fun withAmplitudeScale(
+            @FloatRange(from = 0.0, to = 1.0) newAmplitudeScale: Float,
+        ): PrimitiveAtom =
+            if (amplitudeScale == newAmplitudeScale) {
+                this
+            } else {
+                PrimitiveAtom(type, minSdk, newAmplitudeScale)
+            }
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other !is PrimitiveAtom) return false
+            if (type != other.type) return false
+            if (minSdk != other.minSdk) return false
+            if (amplitudeScale != other.amplitudeScale) return false
+            return true
+        }
+
+        override fun hashCode(): Int = Objects.hash(type, amplitudeScale)
+
+        override fun toString(): String {
+            val typeStr = when (type) {
+                LOW_TICK -> "LowTick"
+                TICK -> "Tick"
+                CLICK -> "Click"
+                SLOW_RISE -> "SlowRise"
+                QUICK_RISE -> "QuickRise"
+                QUICK_FALL -> "QuickFall"
+                SPIN -> "Spin"
+                THUD -> "Thud"
+                else -> type.toString()
+            }
+            return "PrimitiveAtom(type=$typeStr, amplitude=$amplitudeScale)"
+        }
+
+        override fun minSdk(): Int = minSdk
+    }
+
+    /**
+     * A [OffAtom] turns off the vibrator for the specified duration.
+     *
+     * @sample androidx.core.haptics.samples.CompositionSignalOfScaledEffectsAndOff
+     */
+    class OffAtom internal constructor(duration: Duration) : Atom() {
+        /**
+         * The duration for the vibrator to be turned off, in milliseconds.
+         */
+        val durationMillis: Long
+
+        init {
+            require(duration.isFinite() && !duration.isNegative()) {
+                "Composition signal off atom duration must be finite and non-negative: $duration"
+            }
+            durationMillis = duration.inWholeMilliseconds
+        }
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other !is OffAtom) return false
+            if (durationMillis != other.durationMillis) return false
+            return true
+        }
+
+        override fun hashCode(): Int {
+            return durationMillis.hashCode()
+        }
+
+        override fun toString(): String {
+            return "OffAtom(durationMillis=$durationMillis)"
+        }
+
+        override fun minSdk(): Int = Build.VERSION_CODES.R
+    }
+}
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/HapticSignal.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/HapticSignal.kt
new file mode 100644
index 0000000..69ae2c5
--- /dev/null
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/HapticSignal.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.signal
+
+import androidx.core.haptics.VibrationWrapper
+
+/**
+ * A [HapticSignal] describes a generic vibration to be played by a vibrator.
+ *
+ * These signals may represent any number of things, from single shot vibrations to complex
+ * waveforms, device-specific predefined effects or custom vibration patterns.
+ *
+ * A haptic signal can be defined as a [FiniteSignal] or [InfiniteSignal]. Infinite signals will be
+ * played by the vibrator until canceled, while finite signals will stop playing once completed.
+ *
+ * Note: This is a library-restricted representation of a haptic signal that will be mapped to a
+ * platform representation available, like [android.os.VibrationEffect]. This class cannot be
+ * extended or supplemented outside the library, but they can be instantiated from custom extensions
+ * via factory methods.
+ */
+abstract class HapticSignal internal constructor() {
+
+    /**
+     * Returns a [VibrationWrapper] representing this signal, or null if not supported in this SDK
+     * level.
+     */
+    internal abstract fun toVibration(): VibrationWrapper?
+}
+
+/**
+ * A [FiniteSignal] describes a non-infinite haptic signal to be played by a vibrator.
+ */
+abstract class FiniteSignal internal constructor() : HapticSignal()
+
+/**
+ * A [InfiniteSignal] describes a haptic signal that will be played by a vibrator until canceled.
+ */
+abstract class InfiniteSignal internal constructor() : HapticSignal()
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/PredefinedEffect.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/PredefinedEffect.kt
deleted file mode 100644
index abe1b89..0000000
--- a/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/PredefinedEffect.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.core.haptics.signal
-
-/**
- * A [PredefinedEffect] describes a haptic effect to be played by a vibrator.
- *
- * Predefined effects represent common vibration effects that should be identical, regardless of
- * the app they come from, in order to provide a cohesive experience for users across the entire
- * device.
- *
- * They also may be custom tailored to the device hardware in order to provide a better
- * experience than you could otherwise build using the generic building blocks.
- *
- * This will fallback to a generic pattern if one exists and there is no hardware-specific
- * implementation of the effect available.
- */
-class PredefinedEffect private constructor(
-
-    /** The id of the effect to be played. */
-    internal val effectId: Int
-) {
-
-    companion object {
-
-        /**
-         * A standard tick effect.
-         *
-         * This effect is less strong than the [PredefinedClick].
-         */
-        @JvmField
-        val PredefinedTick = PredefinedEffect(2) // VibrationEffect.EFFECT_TICK
-
-        /**
-         * A standard click effect.
-         *
-         * Use this effect as a baseline, as it's the most common type of click effect.
-         */
-        @JvmField
-        val PredefinedClick = PredefinedEffect(0) // VibrationEffect.EFFECT_CLICK
-
-        /**
-         * A heavy click effect.
-         *
-         * This effect is stronger than the [PredefinedClick].
-         */
-        @JvmField
-        val PredefinedHeavyClick = PredefinedEffect(5) // VibrationEffect.EFFECT_HEAVY_CLICK
-
-        /**
-         * A double-click effect.
-         */
-        @JvmField
-        val PredefinedDoubleClick = PredefinedEffect(1) // VibrationEffect.EFFECT_DOUBLE_CLICK
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is PredefinedEffect) return false
-        if (effectId != other.effectId) return false
-        return true
-    }
-
-    override fun hashCode(): Int {
-        return effectId.hashCode()
-    }
-}
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/PredefinedEffectSignal.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/PredefinedEffectSignal.kt
new file mode 100644
index 0000000..a81419a
--- /dev/null
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/PredefinedEffectSignal.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.signal
+
+import android.os.Build
+import androidx.annotation.IntDef
+import androidx.annotation.RestrictTo
+import androidx.core.haptics.VibrationWrapper
+import androidx.core.haptics.impl.HapticSignalConverter
+
+/**
+ * A predefined haptic effect that represents common vibration effects, like clicks and ticks.
+ *
+ * Predefined haptic effects should be identical, regardless of the app they come from, in order to
+ * provide a cohesive experience for users across the entire device. The predefined effects are
+ * based on the [android.os.VibrationEffect.createPredefined] platform API.
+ *
+ * @sample androidx.core.haptics.samples.PlaySystemStandardClick
+ */
+class PredefinedEffectSignal private constructor(
+
+    /**
+     * The type of haptic effect to be played.
+     */
+    @Type internal val type: Int,
+
+    /**
+     * The minimum SDK level where this effect type is available in the platform.
+     */
+    private val minSdk: Int,
+
+) : FiniteSignal() {
+
+    /** Typedef for the [type] attribute. */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @Retention(AnnotationRetention.SOURCE)
+    @IntDef(
+        TICK,
+        CLICK,
+        HEAVY_CLICK,
+        DOUBLE_CLICK,
+    )
+    annotation class Type
+
+    companion object {
+        internal const val TICK = 2 // VibrationEffect.EFFECT_TICK
+        internal const val CLICK = 0 // VibrationEffect.EFFECT_CLICK
+        internal const val HEAVY_CLICK = 5 // VibrationEffect.EFFECT_HEAVY_CLICK
+        internal const val DOUBLE_CLICK = 1 // VibrationEffect.EFFECT_DOUBLE_CLICK
+
+        private val Tick = PredefinedEffectSignal(TICK, Build.VERSION_CODES.Q)
+        private val Click = PredefinedEffectSignal(CLICK, Build.VERSION_CODES.Q)
+        private val HeavyClick = PredefinedEffectSignal(HEAVY_CLICK, Build.VERSION_CODES.Q)
+        private val DoubleClick = PredefinedEffectSignal(DOUBLE_CLICK, Build.VERSION_CODES.Q)
+
+        /**
+         * A standard tick effect.
+         *
+         * This effect is less strong than the [predefinedClick()].
+         */
+        @JvmStatic
+        fun predefinedTick() = Tick
+
+        /**
+         * A standard click effect.
+         *
+         * Use this effect as a baseline, as it's the most common type of click effect.
+         */
+        @JvmStatic
+        fun predefinedClick() = Click
+
+        /**
+         * A heavy click effect.
+         *
+         * This effect is stronger than the [predefinedClick()].
+         */
+        @JvmStatic
+        fun predefinedHeavyClick() = HeavyClick
+
+        /**
+         * A double-click effect.
+         */
+        @JvmStatic
+        fun predefinedDoubleClick() = DoubleClick
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is PredefinedEffectSignal) return false
+        if (type != other.type) return false
+        return true
+    }
+
+    override fun hashCode(): Int {
+        return type.hashCode()
+    }
+
+    override fun toString(): String {
+        val typeStr = when (type) {
+            TICK -> "Tick"
+            CLICK -> "Click"
+            HEAVY_CLICK -> "HeavyClick"
+            DOUBLE_CLICK -> "DoubleClick"
+            else -> type.toString()
+        }
+        return "PredefinedEffectSignal(type=$typeStr)"
+    }
+
+    /**
+     * Returns the minimum SDK level required by the effect type.
+     */
+    internal fun minSdk(): Int = minSdk
+
+    override fun toVibration(): VibrationWrapper? = HapticSignalConverter.toVibration(this)
+}
diff --git a/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/WaveformSignal.kt b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/WaveformSignal.kt
new file mode 100644
index 0000000..3f1b87f
--- /dev/null
+++ b/core/haptics/haptics/src/main/java/androidx/core/haptics/signal/WaveformSignal.kt
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.haptics.signal
+
+import android.os.Build
+import androidx.annotation.FloatRange
+import androidx.annotation.RequiresApi
+import androidx.core.haptics.VibrationWrapper
+import androidx.core.haptics.impl.HapticSignalConverter
+import androidx.core.haptics.signal.WaveformSignal.ConstantVibrationAtom.Companion.DEFAULT_AMPLITUDE
+import java.util.Objects
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.toKotlinDuration
+
+/**
+ * A haptic signal where the vibration parameters change over time.
+ *
+ * Waveform signals may be used to describe step waveforms, defined by a sequence of constant
+ * vibrations played at different strengths. They can also be combined to define a
+ * [RepeatingWaveformSignal], which is an [InfiniteSignal] that repeats a waveform until the
+ * vibration is canceled.
+ *
+ * @sample androidx.core.haptics.samples.AmplitudeWaveform
+ * @sample androidx.core.haptics.samples.PatternThenRepeatAmplitudeWaveform
+ */
+class WaveformSignal(
+
+    /**
+     * The waveform signal atoms that describes the vibration parameters over time.
+     */
+    val atoms: List<Atom>,
+
+) : FiniteSignal() {
+    init {
+        require(atoms.isNotEmpty()) { "Haptic signals cannot be empty" }
+    }
+
+    companion object {
+
+        /**
+         * Returns a [WaveformSignal] created with given waveform atoms.
+         *
+         * Use [on] and [off] to create atoms.
+         *
+         * @sample androidx.core.haptics.samples.AmplitudeWaveform
+         *
+         * @param atoms The [WaveformSignal.Atom] instances that define the [WaveformSignal].
+         */
+        @JvmStatic
+        fun waveformOf(vararg atoms: Atom): WaveformSignal =
+            WaveformSignal(atoms.toList())
+
+        /**
+         * Returns a [RepeatingWaveformSignal] created with given waveform atoms.
+         *
+         * Repeating waveforms should include any desired loop delay as an [off] atom at the end of
+         * the atom list.
+         *
+         * @sample androidx.core.haptics.samples.RepeatingAmplitudeWaveform
+         *
+         * @param atoms The [WaveformSignal.Atom] instances that define the
+         *   [RepeatingWaveformSignal].
+         */
+        @JvmStatic
+        fun repeatingWaveformOf(vararg atoms: Atom): RepeatingWaveformSignal =
+            waveformOf(*atoms).repeat()
+
+        /**
+         * Returns a [WaveformSignal.Atom] that turns off the vibrator for the specified duration.
+         *
+         * @sample androidx.core.haptics.samples.PatternWaveform
+         *
+         * @param duration The duration the vibrator should be turned off.
+         */
+        @RequiresApi(Build.VERSION_CODES.O)
+        @JvmStatic
+        fun off(duration: java.time.Duration) =
+            ConstantVibrationAtom(duration.toKotlinDuration(), amplitude = 0f)
+
+        /**
+         * Returns a [WaveformSignal.Atom] that turns off the vibrator for the specified duration.
+         *
+         * @sample androidx.core.haptics.samples.PatternWaveform
+         *
+         * @param durationMillis The duration the vibrator should be turned off, in milliseconds.
+         */
+        @JvmStatic
+        fun off(durationMillis: Long) =
+            ConstantVibrationAtom(durationMillis.milliseconds, amplitude = 0f)
+
+        /**
+         * Returns a [WaveformSignal.Atom] that turns on the vibrator for the specified duration at
+         * a device-specific default amplitude.
+         *
+         * @sample androidx.core.haptics.samples.PatternWaveform
+         *
+         * @param duration The duration for the vibration.
+         */
+        @RequiresApi(Build.VERSION_CODES.O)
+        @JvmStatic
+        fun on(duration: java.time.Duration) =
+            ConstantVibrationAtom(duration.toKotlinDuration(), DEFAULT_AMPLITUDE)
+
+        /**
+         * Returns a [WaveformSignal.Atom] that turns on the vibrator for the specified duration at
+         * a device-specific default amplitude.
+         *
+         * @sample androidx.core.haptics.samples.PatternWaveform
+         *
+         * @param durationMillis The duration for the vibration, in milliseconds.
+         */
+        @JvmStatic
+        fun on(durationMillis: Long) =
+            ConstantVibrationAtom(durationMillis.milliseconds, DEFAULT_AMPLITUDE)
+
+        /**
+         * Returns a [WaveformSignal.Atom] that turns on the vibrator for the specified duration at
+         * the specified amplitude.
+         *
+         * @sample androidx.core.haptics.samples.AmplitudeWaveform
+         *
+         * @param duration The duration for the vibration.
+         * @param amplitude The vibration strength, with 1 representing maximum amplitude, and 0
+         *   representing off - equivalent to calling [off].
+         */
+        @RequiresApi(Build.VERSION_CODES.O)
+        @JvmStatic
+        fun on(duration: java.time.Duration, @FloatRange(from = 0.0, to = 1.0) amplitude: Float) =
+            ConstantVibrationAtom(duration.toKotlinDuration(), amplitude)
+
+        /**
+         * Returns a [WaveformSignal.Atom] that turns on the vibrator for the specified duration at
+         * the specified amplitude.
+         *
+         * @sample androidx.core.haptics.samples.AmplitudeWaveform
+         *
+         * @param durationMillis The duration for the vibration, in milliseconds.
+         * @param amplitude The vibration strength, with 1 representing maximum amplitude, and 0
+         *   representing off - equivalent to calling [off].
+         */
+        @JvmStatic
+        fun on(durationMillis: Long, @FloatRange(from = 0.0, to = 1.0) amplitude: Float) =
+            ConstantVibrationAtom(durationMillis.milliseconds, amplitude)
+    }
+
+    /**
+     * Returns a [RepeatingWaveformSignal] to play this waveform on repeat until it's canceled.
+     *
+     * @sample androidx.core.haptics.samples.PatternWaveformRepeat
+     */
+    fun repeat(): RepeatingWaveformSignal =
+        RepeatingWaveformSignal(initialWaveform = null, repeatingWaveform = this)
+
+    /**
+     * Returns a [RepeatingWaveformSignal] that starts with this waveform signal then plays the
+     * given waveform signal on repeat until the vibration is canceled.
+     *
+     * @sample androidx.core.haptics.samples.PatternThenRepeatExistingWaveform
+     *
+     * @param waveformToRepeat The waveform to be played on repeat after this waveform.
+     */
+    fun thenRepeat(waveformToRepeat: WaveformSignal): RepeatingWaveformSignal =
+        RepeatingWaveformSignal(initialWaveform = this, repeatingWaveform = waveformToRepeat)
+
+    /**
+     * Returns a [RepeatingWaveformSignal] that starts with this waveform signal then plays the
+     * given waveform atoms on repeat until the vibration is canceled.
+     *
+     * @sample androidx.core.haptics.samples.PatternThenRepeatAmplitudeWaveform
+     *
+     * @param atoms The [WaveformSignal.Atom] instances that define the repeating [WaveformSignal]
+     *   to be played after this waveform.
+     */
+    fun thenRepeat(vararg atoms: Atom): RepeatingWaveformSignal =
+        thenRepeat(waveformOf(*atoms))
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is WaveformSignal) return false
+        if (atoms != other.atoms) return false
+        return true
+    }
+
+    override fun hashCode(): Int {
+        return atoms.hashCode()
+    }
+
+    override fun toString(): String {
+        return "WaveformSignal(${atoms.joinToString()})"
+    }
+
+    override fun toVibration(): VibrationWrapper? =
+        HapticSignalConverter.toVibration(initialWaveform = this, repeatingWaveform = null)
+
+    /**
+     * A [WaveformSignal.Atom] is a building block for creating a [WaveformSignal].
+     *
+     * Waveform signal atoms describe how vibration parameters change over time. They can describe
+     * a constant vibration sustained for a fixed duration, for example, which can be used to create
+     * a step waveform. They can also be used to describe simpler on-off vibration patterns.
+     *
+     * @sample androidx.core.haptics.samples.PatternWaveform
+     * @sample androidx.core.haptics.samples.AmplitudeWaveform
+     */
+    abstract class Atom internal constructor()
+
+    /**
+     * A [ConstantVibrationAtom] plays a constant vibration for the specified period of time.
+     *
+     * Constant vibrations can be played in sequence to create custom waveform signals.
+     *
+     * The amplitude determines the strength of the vibration, defined as a value in the range
+     * [0f..1f]. Zero amplitude implies the vibrator motor should be off. The amplitude can also be
+     * defined by [DEFAULT_AMPLITUDE], which will vibrate constantly at a hardware-specific default
+     * vibration strength.
+     *
+     * @sample androidx.core.haptics.samples.PatternWaveform
+     * @sample androidx.core.haptics.samples.AmplitudeWaveform
+     */
+    class ConstantVibrationAtom internal constructor(
+
+        duration: Duration,
+
+        /**
+         * The vibration strength.
+         *
+         * Zero amplitude turns the vibrator off for the specified duration, and [DEFAULT_AMPLITUDE]
+         * uses a hardware-specific default vibration strength.
+         */
+        val amplitude: Float,
+
+    ) : Atom() {
+        /**
+         * The duration to sustain the constant vibration, in milliseconds.
+         */
+        val durationMillis: Long
+
+        init {
+            require(duration.isFinite() && !duration.isNegative()) {
+                "Constant vibration duration must be finite and non-negative: $duration"
+            }
+            require(amplitude in (0.0..1.0) || amplitude == DEFAULT_AMPLITUDE) {
+                "Constant vibration amplitude must be in [0,1]: $amplitude"
+            }
+            durationMillis = duration.inWholeMilliseconds
+        }
+
+        companion object {
+            /**
+             * The [amplitude] value that represents a hardware-specific default vibration strength.
+             */
+            const val DEFAULT_AMPLITUDE: Float = -1f
+        }
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other !is ConstantVibrationAtom) return false
+            if (durationMillis != other.durationMillis) return false
+            if (amplitude != other.amplitude) return false
+            return true
+        }
+
+        override fun hashCode(): Int {
+            return Objects.hash(durationMillis, amplitude)
+        }
+
+        override fun toString(): String {
+            return "ConstantVibrationAtom(durationMillis=$durationMillis" +
+                ", amplitude=${if (amplitude == DEFAULT_AMPLITUDE) "default" else amplitude})"
+        }
+    }
+}
+
+/**
+ * A [RepeatingWaveformSignal] describes an infinite haptic signal where a waveform signal is played
+ * on repeat until canceled.
+ *
+ * A repeating waveform signal has an optional initial [WaveformSignal] that plays once before the
+ * repeating waveform signal is played on repeat until the vibration is canceled.
+ *
+ * @sample androidx.core.haptics.samples.RepeatingAmplitudeWaveform
+ */
+class RepeatingWaveformSignal internal constructor(
+
+    /**
+     * The optional initial waveform signal to be played once at the beginning of the vibration.
+     */
+    val initialWaveform: WaveformSignal?,
+
+    /**
+     * The waveform signal to be repeated after the initial waveform.
+     */
+    val repeatingWaveform: WaveformSignal,
+
+) : InfiniteSignal() {
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is RepeatingWaveformSignal) return false
+        if (initialWaveform != other.initialWaveform) return false
+        if (repeatingWaveform != other.repeatingWaveform) return false
+        return true
+    }
+
+    override fun hashCode(): Int {
+        return Objects.hash(initialWaveform, repeatingWaveform)
+    }
+
+    override fun toString(): String {
+        return "RepeatingWaveformSignal(initial=$initialWaveform, repeating=$repeatingWaveform)"
+    }
+
+    override fun toVibration(): VibrationWrapper? =
+        HapticSignalConverter.toVibration(
+            initialWaveform = initialWaveform,
+            repeatingWaveform = repeatingWaveform,
+        )
+}
diff --git a/credentials/credentials-fido/credentials-fido/api/current.txt b/credentials/credentials-fido/credentials-fido/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/credentials/credentials-fido/credentials-fido/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/credentials/credentials-fido/credentials-fido/api/res-current.txt b/credentials/credentials-fido/credentials-fido/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/credentials/credentials-fido/credentials-fido/api/res-current.txt
diff --git a/credentials/credentials-fido/credentials-fido/api/restricted_current.txt b/credentials/credentials-fido/credentials-fido/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/credentials/credentials-fido/credentials-fido/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/credentials/credentials-fido/credentials-fido/build.gradle b/credentials/credentials-fido/credentials-fido/build.gradle
new file mode 100644
index 0000000..0fa93f9
--- /dev/null
+++ b/credentials/credentials-fido/credentials-fido/build.gradle
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.build.LibraryType
+import androidx.build.Publish
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+    api(libs.kotlinStdlib)
+    api project(":credentials:credentials")
+
+    androidTestImplementation(libs.junit)
+    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.testRules)
+    androidTestImplementation(libs.mockitoAndroid)
+    androidTestImplementation(libs.truth)
+    androidTestImplementation(libs.multidex)
+    androidTestImplementation(project(":internal-testutils-truth"))
+    androidTestImplementation(libs.kotlinCoroutinesAndroid)
+    androidTestImplementation("androidx.core:core-ktx:1.10.0")
+}
+
+android {
+    namespace "androidx.credentials.fido"
+
+    defaultConfig {
+        minSdkVersion 19
+        multiDexEnabled = true
+    }
+}
+
+androidx {
+    name = "credentials-fido"
+    type = LibraryType.PUBLISHED_LIBRARY
+    inceptionYear = "2023"
+    description = "Util library for apps using FIDO"
+    publish = Publish.SNAPSHOT_AND_RELEASE
+}
diff --git a/credentials/credentials-fido/credentials-fido/proguard-rules.pro b/credentials/credentials-fido/credentials-fido/proguard-rules.pro
new file mode 100644
index 0000000..4cced1e
--- /dev/null
+++ b/credentials/credentials-fido/credentials-fido/proguard-rules.pro
@@ -0,0 +1,4 @@
+-if class androidx.credentials.CredentialManager
+-keep class androidx.credentials.passkeys.** {
+  *;
+}
\ No newline at end of file
diff --git a/credentials/credentials-fido/credentials-fido/src/androidTest/AndroidManifest.xml b/credentials/credentials-fido/credentials-fido/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..dc727a5
--- /dev/null
+++ b/credentials/credentials-fido/credentials-fido/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <application>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/credentials/credentials-fido/credentials-fido/src/main/AndroidManifest.xml b/credentials/credentials-fido/credentials-fido/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d8a4ecd
--- /dev/null
+++ b/credentials/credentials-fido/credentials-fido/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:tools="http://schemas.android.com/tools"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <application>
+    </application>
+</manifest>
diff --git a/credentials/credentials-fido/credentials-fido/src/main/androidx/credentials/androidx-credentials-credentials-fido-documentation.md b/credentials/credentials-fido/credentials-fido/src/main/androidx/credentials/androidx-credentials-credentials-fido-documentation.md
new file mode 100644
index 0000000..b0450b7
--- /dev/null
+++ b/credentials/credentials-fido/credentials-fido/src/main/androidx/credentials/androidx-credentials-credentials-fido-documentation.md
@@ -0,0 +1,7 @@
+# Module root
+
+CREDENTIALS CREDENTIALS FIDO
+
+# Package androidx.credentials.fido
+
+This package contains utility functions for FIDO app developers.
\ No newline at end of file
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java b/credentials/credentials-fido/credentials-fido/src/main/java/androidx/credentials/fido/EmptyActivity.kt
similarity index 62%
copy from camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
copy to credentials/credentials-fido/credentials-fido/src/main/java/androidx/credentials/fido/EmptyActivity.kt
index 7a355f8..20839f9 100644
--- a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
+++ b/credentials/credentials-fido/credentials-fido/src/main/java/androidx/credentials/fido/EmptyActivity.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,12 @@
  * limitations under the License.
  */
 
-package androidx.camera.effects;
+package androidx.credentials.fido
 
-import androidx.annotation.RestrictTo;
+import android.app.Activity
+import androidx.annotation.RestrictTo
 
-/**
- * Provides a portrait post-processing effect.
- *
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class Portrait {
-    // TODO: implement this
-}
+/** Remove this class when we add the code (b/292103206). */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@Suppress("Deprecation", "ForbiddenSuperClass")
+open class EmptyActivity : Activity()
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/TestUtils.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/TestUtils.kt
index f1cf823..4c5226d 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/TestUtils.kt
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/TestUtils.kt
@@ -75,13 +75,16 @@
                 val superValues = superset.get(key)
 
                 if ((values::class.java != superValues::class.java || values::class.java !=
-                    requiredValues::class.java) && requiredValues !is Boolean
+                        requiredValues::class.java) && requiredValues !is Boolean
                 ) {
                     return false
                 }
                 if (requiredValues is JSONObject) {
-                    if (!isSubsetJson(superValues as JSONObject, values as JSONObject,
-                            requiredValues)) {
+                    if (!isSubsetJson(
+                            superValues as JSONObject, values as JSONObject,
+                            requiredValues
+                        )
+                    ) {
                         return false
                     }
                 } else if (values is JSONArray) {
@@ -147,6 +150,7 @@
             ConnectionResult.SERVICE_INVALID, ConnectionResult.SERVICE_MISSING,
             ConnectionResult.SERVICE_MISSING_PERMISSION, ConnectionResult.SERVICE_UPDATING,
             ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED, ConnectionResult.SIGN_IN_FAILED,
-            ConnectionResult.SIGN_IN_REQUIRED, ConnectionResult.TIMEOUT)
+            ConnectionResult.SIGN_IN_REQUIRED, ConnectionResult.TIMEOUT
+        )
     }
 }
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CreatePublicKeyCredentialControllerTestUtils.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CreatePublicKeyCredentialControllerTestUtils.kt
index de8ef2b..2dbf889 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CreatePublicKeyCredentialControllerTestUtils.kt
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/CreatePublicKeyCredentialControllerTestUtils.kt
@@ -17,6 +17,7 @@
 package androidx.credentials.playservices.createkeycredential
 
 import androidx.credentials.playservices.TestUtils
+import androidx.credentials.playservices.controllers.CreatePublicKeyCredential.PublicKeyCredentialControllerUtility
 import com.google.android.gms.fido.common.Transport
 import com.google.android.gms.fido.fido2.api.common.PublicKeyCredentialCreationOptions
 import org.json.JSONArray
@@ -26,6 +27,8 @@
 class CreatePublicKeyCredentialControllerTestUtils {
     companion object {
 
+        const val TAG = "PasskeyTestUtils"
+
          // optional and not required key 'transports' is missing in the JSONObject that composes
          // up the JSONArray found at key 'excludeCredentials'
          const val OPTIONAL_FIELD_MISSING_OPTIONAL_SUBFIELD = ("{\"rp\": {\"name\": " +
@@ -323,5 +326,122 @@
                 }
             }
         }
+
+        /**
+         * Helps generate a PublicKeyCredential response json format to start tests with, locally
+         * for example.
+         *
+         * Usage details as follows:
+         *
+         *     val byteArrayClientDataJson = byteArrayOf(0x48, 101, 108, 108, 111)
+         *     val byteArrayAuthenticatorData = byteArrayOf(0x48, 101, 108, 108, 112)
+         *     val byteArraySignature = byteArrayOf(0x48, 101, 108, 108, 113)
+         *     val byteArrayUserHandle = byteArrayOf(0x48, 101, 108, 108, 114)
+         *     val publicKeyCredId = "id"
+         *     val publicKeyCredRawId = byteArrayOf(0x48, 101, 108, 108, 115)
+         *     val publicKeyCredType = "type"
+         *     val authenticatorAttachment = "platform"
+         *     val hasClientExtensionOutputs = true
+         *     val isDiscoverableCredential = true
+         *     val expectedClientExtensions = "{\"credProps\":{\"rk\":true}}"
+         *
+         *     val json = PublicKeyCredentialControllerUtility.beginSignInAssertionResponse(
+         *       byteArrayClientDataJson,
+         *       byteArrayAuthenticatorData,
+         *       byteArraySignature,
+         *       byteArrayUserHandle,
+         *       publicKeyCredId,
+         *       publicKeyCredRawId,
+         *       publicKeyCredType,
+         *       authenticatorAttachment,
+         *       hasClientExtensionOutputs,
+         *       isDiscoverableCredential
+         *     )
+         *
+         * The json can be used as necessary, even if only to generate a log with which to pull
+         * the string from (to then further use that string in other test cases).
+         */
+        fun getPublicKeyCredentialResponseGenerator(
+            clientDataJSON: ByteArray,
+            authenticatorData: ByteArray,
+            signature: ByteArray,
+            userHandle: ByteArray?,
+            publicKeyCredId: String,
+            publicKeyCredRawId: ByteArray,
+            publicKeyCredType: String,
+            authenticatorAttachment: String?,
+            hasClientExtensionResults: Boolean,
+            isDiscoverableCredential: Boolean?
+        ): JSONObject {
+            val json = JSONObject()
+            val responseJson = JSONObject()
+            responseJson.put(
+                PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_DATA,
+                PublicKeyCredentialControllerUtility.b64Encode(clientDataJSON)
+            )
+            responseJson.put(
+                PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_DATA,
+                PublicKeyCredentialControllerUtility.b64Encode(authenticatorData)
+            )
+            responseJson.put(
+                PublicKeyCredentialControllerUtility.JSON_KEY_SIGNATURE,
+                PublicKeyCredentialControllerUtility.b64Encode(signature)
+            )
+            userHandle?.let {
+                responseJson.put(
+                    PublicKeyCredentialControllerUtility.JSON_KEY_USER_HANDLE,
+                    PublicKeyCredentialControllerUtility.b64Encode(userHandle)
+                )
+            }
+            json.put(PublicKeyCredentialControllerUtility.JSON_KEY_RESPONSE, responseJson)
+            json.put(PublicKeyCredentialControllerUtility.JSON_KEY_ID, publicKeyCredId)
+            json.put(
+                PublicKeyCredentialControllerUtility.JSON_KEY_RAW_ID,
+                PublicKeyCredentialControllerUtility.b64Encode(publicKeyCredRawId)
+            )
+            json.put(PublicKeyCredentialControllerUtility.JSON_KEY_TYPE, publicKeyCredType)
+            addOptionalAuthenticatorAttachmentAndRequiredExtensions(
+                authenticatorAttachment,
+                hasClientExtensionResults,
+                isDiscoverableCredential,
+                json
+            )
+            return json;
+        }
+
+        // This can be shared by both get and create flow response parsers, fills 'json'.
+        private fun addOptionalAuthenticatorAttachmentAndRequiredExtensions(
+            authenticatorAttachment: String?,
+            hasClientExtensionResults: Boolean,
+            isDiscoverableCredential: Boolean?,
+            json: JSONObject
+        ) {
+
+            json.putOpt(
+                PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_ATTACHMENT,
+                authenticatorAttachment
+            )
+
+            val clientExtensionsJson = JSONObject()
+
+            if (hasClientExtensionResults) {
+                if (isDiscoverableCredential != null) {
+                    val credPropsObject = JSONObject()
+                    credPropsObject.put(
+                        PublicKeyCredentialControllerUtility.JSON_KEY_RK,
+                        isDiscoverableCredential
+                    )
+                    clientExtensionsJson.put(
+                        PublicKeyCredentialControllerUtility.JSON_KEY_CRED_PROPS,
+                        credPropsObject
+                    )
+                }
+            }
+
+            json.put(
+                PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_EXTENSION_RESULTS,
+                clientExtensionsJson
+            )
+        }
     }
 }
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/PublicKeyCredentialControllerUtilityTest.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/PublicKeyCredentialControllerUtilityTest.kt
index 80617b9..17af5dc 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/PublicKeyCredentialControllerUtilityTest.kt
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/createpublickeycredential/PublicKeyCredentialControllerUtilityTest.kt
@@ -149,226 +149,6 @@
   }
 
   @Test
-  fun toAssertPasskeyResponse_authenticatorAssertionResponse_success() {
-    val byteArrayClientDataJson = byteArrayOf(0x48, 101, 108, 108, 111)
-    val byteArrayAuthenticatorData = byteArrayOf(0x48, 101, 108, 108, 112)
-    val byteArraySignature = byteArrayOf(0x48, 101, 108, 108, 113)
-    val byteArrayUserHandle = byteArrayOf(0x48, 101, 108, 108, 114)
-    val json = JSONObject()
-    val publicKeyCredId = "id"
-    val publicKeyCredRawId = byteArrayOf(0x48, 101, 108, 108, 115)
-    val publicKeyCredType = "type"
-    val authenticatorAttachment = "platform"
-    val hasClientExtensionOutputs = true
-    val isDiscoverableCredential = true
-    val expectedClientExtensions = "{\"credProps\":{\"rk\":true}}"
-
-    PublicKeyCredentialControllerUtility.beginSignInAssertionResponse(
-      byteArrayClientDataJson,
-      byteArrayAuthenticatorData,
-      byteArraySignature,
-      byteArrayUserHandle,
-      json,
-      publicKeyCredId,
-      publicKeyCredRawId,
-      publicKeyCredType,
-      authenticatorAttachment,
-      hasClientExtensionOutputs,
-      isDiscoverableCredential
-    )
-
-    assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_ID))
-      .isEqualTo(publicKeyCredId)
-    assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_RAW_ID))
-      .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(publicKeyCredRawId))
-    assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_TYPE))
-      .isEqualTo(publicKeyCredType)
-    assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_ATTACHMENT))
-      .isEqualTo(authenticatorAttachment)
-    assertThat(
-        json
-          .getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_EXTENSION_RESULTS)
-          .toString()
-      )
-      .isEqualTo(expectedClientExtensions)
-
-    // There is some embedded JSON so we should make sure we test that.
-    var embeddedResponse =
-      json.getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_RESPONSE)
-    assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_DATA))
-      .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArrayClientDataJson))
-    assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_DATA))
-      .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArrayAuthenticatorData))
-    assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_SIGNATURE))
-      .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArraySignature))
-    assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_USER_HANDLE))
-      .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArrayUserHandle))
-
-    // ClientExtensions are another group of embedded JSON
-    var clientExtensions =
-      json.getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_EXTENSION_RESULTS)
-    assertThat(clientExtensions.get(PublicKeyCredentialControllerUtility.JSON_KEY_CRED_PROPS))
-      .isNotNull()
-    assertThat(
-        clientExtensions
-          .getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_CRED_PROPS)
-          .getBoolean(PublicKeyCredentialControllerUtility.JSON_KEY_RK)
-      )
-      .isTrue()
-  }
-
-  @Test
-  fun toAssertPasskeyResponse_authenticatorAssertionResponse_noUserHandle_success() {
-    val byteArrayClientDataJson = byteArrayOf(0x48, 101, 108, 108, 111)
-    val byteArrayAuthenticatorData = byteArrayOf(0x48, 101, 108, 108, 112)
-    val byteArraySignature = byteArrayOf(0x48, 101, 108, 108, 113)
-    val json = JSONObject()
-    val publicKeyCredId = "id"
-    val publicKeyCredRawId = byteArrayOf(0x48, 101, 108, 108, 115)
-    val publicKeyCredType = "type"
-    val authenticatorAttachment = "platform"
-    val hasClientExtensionOutputs = false
-
-    PublicKeyCredentialControllerUtility.beginSignInAssertionResponse(
-      byteArrayClientDataJson,
-      byteArrayAuthenticatorData,
-      byteArraySignature,
-      null,
-      json,
-      publicKeyCredId,
-      publicKeyCredRawId,
-      publicKeyCredType,
-      authenticatorAttachment,
-      hasClientExtensionOutputs,
-      null
-    )
-
-    assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_ID))
-      .isEqualTo(publicKeyCredId)
-    assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_RAW_ID))
-      .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(publicKeyCredRawId))
-    assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_TYPE))
-      .isEqualTo(publicKeyCredType)
-    assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_ATTACHMENT))
-      .isEqualTo(authenticatorAttachment)
-    assertThat(
-        json
-          .getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_EXTENSION_RESULTS)
-          .toString()
-      )
-      .isEqualTo(JSONObject().toString())
-
-    // There is some embedded JSON so we should make sure we test that.
-    var embeddedResponse =
-      json.getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_RESPONSE)
-    assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_DATA))
-      .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArrayClientDataJson))
-    assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_DATA))
-      .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArrayAuthenticatorData))
-    assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_SIGNATURE))
-      .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArraySignature))
-    assertThat(embeddedResponse.has(PublicKeyCredentialControllerUtility.JSON_KEY_USER_HANDLE))
-      .isFalse()
-  }
-
-  @Test
-  fun toAssertPasskeyResponse_authenticatorAssertionResponse_noAuthenticatorAttachment_success() {
-    val byteArrayClientDataJson = byteArrayOf(0x48, 101, 108, 108, 111)
-    val byteArrayAuthenticatorData = byteArrayOf(0x48, 101, 108, 108, 112)
-    val byteArraySignature = byteArrayOf(0x48, 101, 108, 108, 113)
-    val json = JSONObject()
-    val publicKeyCredId = "id"
-    val publicKeyCredRawId = byteArrayOf(0x48, 101, 108, 108, 115)
-    val publicKeyCredType = "type"
-    val hasClientExtensionOutputs = false
-
-    PublicKeyCredentialControllerUtility.beginSignInAssertionResponse(
-      byteArrayClientDataJson,
-      byteArrayAuthenticatorData,
-      byteArraySignature,
-      null,
-      json,
-      publicKeyCredId,
-      publicKeyCredRawId,
-      publicKeyCredType,
-      null,
-      hasClientExtensionOutputs,
-      null
-    )
-
-    assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_ID))
-      .isEqualTo(publicKeyCredId)
-    assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_RAW_ID))
-      .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(publicKeyCredRawId))
-    assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_TYPE))
-      .isEqualTo(publicKeyCredType)
-    assertThat(json.optJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_ATTACHMENT))
-      .isNull()
-    assertThat(
-        json
-          .getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_EXTENSION_RESULTS)
-          .toString()
-      )
-      .isEqualTo(JSONObject().toString())
-
-    // There is some embedded JSON so we should make sure we test that.
-    var embeddedResponse =
-      json.getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_RESPONSE)
-    assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_DATA))
-      .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArrayClientDataJson))
-    assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_DATA))
-      .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArrayAuthenticatorData))
-    assertThat(embeddedResponse.get(PublicKeyCredentialControllerUtility.JSON_KEY_SIGNATURE))
-      .isEqualTo(PublicKeyCredentialControllerUtility.b64Encode(byteArraySignature))
-    assertThat(embeddedResponse.has(PublicKeyCredentialControllerUtility.JSON_KEY_USER_HANDLE))
-      .isFalse()
-  }
-
-  @Test
-  fun toCreatePasskeyResponseJson_addOptionalAuthenticatorAttachmentAndRequiredExt() {
-    val json = JSONObject()
-
-    PublicKeyCredentialControllerUtility.addOptionalAuthenticatorAttachmentAndRequiredExtensions(
-      "attachment",
-      true,
-      true,
-      json
-    )
-
-    var clientExtensionResults =
-      json.getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_EXTENSION_RESULTS)
-    var credPropsObject =
-      clientExtensionResults.getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_CRED_PROPS)
-
-    assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_ATTACHMENT))
-      .isEqualTo("attachment")
-    assertThat(credPropsObject.get(PublicKeyCredentialControllerUtility.JSON_KEY_RK))
-      .isEqualTo(true)
-  }
-
-  @Test
-  fun toCreatePasskeyResponseJson_addOptionalAuthenticatorAttachmentAndRequiredExt_noClientExt() {
-    val json = JSONObject()
-
-    PublicKeyCredentialControllerUtility.addOptionalAuthenticatorAttachmentAndRequiredExtensions(
-      "attachment",
-      false,
-      null,
-      json
-    )
-
-    var clientExtensionResults =
-      json.getJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_CLIENT_EXTENSION_RESULTS)
-
-    assertThat(json.get(PublicKeyCredentialControllerUtility.JSON_KEY_AUTH_ATTACHMENT))
-      .isEqualTo("attachment")
-    assertThat(
-        clientExtensionResults.optJSONObject(PublicKeyCredentialControllerUtility.JSON_KEY_RK)
-      )
-      .isNull()
-  }
-
-  @Test
   fun toCreatePasskeyResponseJson_addAuthenticatorAttestationResponse_success() {
     val json = JSONObject()
     val byteArrayClientDataJson = byteArrayOf(0x48, 101, 108, 108, 111)
@@ -417,16 +197,16 @@
       )
     var output = PublicKeyCredentialControllerUtility.convertJSON(json)
 
-    assertThat(output.getUser().getId()).isNotEmpty()
-    assertThat(output.getUser().getName()).isEqualTo("Name of User")
-    assertThat(output.getUser().getDisplayName()).isEqualTo("Display Name of User")
-    assertThat(output.getUser().getIcon()).isEqualTo("icon.png")
-    assertThat(output.getChallenge()).isNotEmpty()
-    assertThat(output.getRp().getId()).isNotEmpty()
-    assertThat(output.getRp().getName()).isEqualTo("Name of RP")
-    assertThat(output.getRp().getIcon()).isEqualTo("rpicon.png")
-    assertThat(output.getParameters().get(0).getAlgorithmIdAsInteger()).isEqualTo(-7)
-    assertThat(output.getParameters().get(0).getTypeAsString()).isEqualTo("public-key")
+    assertThat(output.user.id).isNotEmpty()
+    assertThat(output.user.name).isEqualTo("Name of User")
+    assertThat(output.user.displayName).isEqualTo("Display Name of User")
+    assertThat(output.user.icon).isEqualTo("icon.png")
+    assertThat(output.challenge).isNotEmpty()
+    assertThat(output.rp.id).isNotEmpty()
+    assertThat(output.rp.name).isEqualTo("Name of RP")
+    assertThat(output.rp.icon).isEqualTo("rpicon.png")
+    assertThat(output.parameters[0].algorithmIdAsInteger).isEqualTo(-7)
+    assertThat(output.parameters[0].typeAsString).isEqualTo("public-key")
   }
 
   @Test
@@ -680,9 +460,8 @@
       )
     var output = PublicKeyCredentialControllerUtility.convertJSON(json)
 
-    assertThat(output.getAuthenticationExtensions()
-        !!.getFidoAppIdExtension()!!.getAppId()).isEqualTo("https://www.android.com/appid1")
-    assertThat(output.getAuthenticationExtensions()
-        !!.getUserVerificationMethodExtension()!!.getUvm()).isTrue()
+    assertThat(output.authenticationExtensions!!.fidoAppIdExtension!!.appId)
+      .isEqualTo("https://www.android.com/appid1")
+    assertThat(output.authenticationExtensions!!.userVerificationMethodExtension!!.uvm).isTrue()
   }
 }
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
index 1a37158..e86df83 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/CredentialProviderCreatePublicKeyCredentialController.kt
@@ -186,9 +186,12 @@
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
     public override fun convertResponseToCredentialManager(response: PublicKeyCredential):
         CreateCredentialResponse {
-        return CreatePublicKeyCredentialResponse(
-            PublicKeyCredentialControllerUtility.toCreatePasskeyResponseJson(response)
-        )
+            try {
+                return CreatePublicKeyCredentialResponse(response.toJson())
+            } catch (t: Throwable) {
+                throw CreateCredentialUnknownException("The PublicKeyCredential response json " +
+                    "had an unexpected exception when parsing: ${t.message}")
+            }
     }
 
     private fun JSONExceptionToPKCError(exception: JSONException):
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/PublicKeyCredentialControllerUtility.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/PublicKeyCredentialControllerUtility.kt
index 4b38772..b38500b 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/PublicKeyCredentialControllerUtility.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CreatePublicKeyCredential/PublicKeyCredentialControllerUtility.kt
@@ -24,6 +24,7 @@
 import androidx.credentials.exceptions.CreateCredentialException
 import androidx.credentials.exceptions.GetCredentialCancellationException
 import androidx.credentials.exceptions.GetCredentialException
+import androidx.credentials.exceptions.GetCredentialUnknownException
 import androidx.credentials.exceptions.domerrors.AbortError
 import androidx.credentials.exceptions.domerrors.ConstraintError
 import androidx.credentials.exceptions.domerrors.DataError
@@ -45,7 +46,6 @@
 import com.google.android.gms.fido.fido2.api.common.AttestationConveyancePreference
 import com.google.android.gms.fido.fido2.api.common.AuthenticationExtensions
 import com.google.android.gms.fido.fido2.api.common.AuthenticatorAssertionResponse
-import com.google.android.gms.fido.fido2.api.common.AuthenticatorAttestationResponse
 import com.google.android.gms.fido.fido2.api.common.AuthenticatorErrorResponse
 import com.google.android.gms.fido.fido2.api.common.AuthenticatorResponse
 import com.google.android.gms.fido.fido2.api.common.AuthenticatorSelectionCriteria
@@ -136,39 +136,6 @@
       return builder.build()
     }
 
-    /** Converts the response from fido back to json so it can be passed into CredentialManager. */
-    fun toCreatePasskeyResponseJson(cred: PublicKeyCredential): String {
-      val json = JSONObject()
-      val authenticatorResponse = cred.response
-      if (authenticatorResponse is AuthenticatorAttestationResponse) {
-        val transportArray = convertToProperNamingScheme(authenticatorResponse)
-        addAuthenticatorAttestationResponse(
-          authenticatorResponse.clientDataJSON,
-          authenticatorResponse.attestationObject,
-          transportArray,
-          json
-        )
-      } else {
-        Log.e(
-          TAG,
-          "Authenticator response expected registration response but " +
-            "got: ${authenticatorResponse.javaClass.name}"
-        )
-      }
-
-      addOptionalAuthenticatorAttachmentAndRequiredExtensions(
-        cred.authenticatorAttachment,
-        cred.clientExtensionResults != null,
-        cred.clientExtensionResults?.credProps?.isDiscoverableCredential,
-        json
-      )
-
-      json.put(JSON_KEY_ID, cred.id)
-      json.put(JSON_KEY_RAW_ID, b64Encode(cred.rawId))
-      json.put(JSON_KEY_TYPE, cred.type)
-      return json.toString()
-    }
-
     internal fun addAuthenticatorAttestationResponse(
       clientDataJSON: ByteArray,
       attestationObject: ByteArray,
@@ -182,52 +149,6 @@
       json.put(JSON_KEY_RESPONSE, responseJson)
     }
 
-    private fun convertToProperNamingScheme(
-      authenticatorResponse: AuthenticatorAttestationResponse
-    ): Array<out String> {
-      val transportArray = authenticatorResponse.transports
-      var ix = 0
-      for (transport in transportArray) {
-        if (transport == "cable") {
-          transportArray[ix] = "hybrid"
-        }
-        ix += 1
-      }
-      return transportArray
-    }
-
-    // This can be shared by both get and create flow response parsers
-    internal fun addOptionalAuthenticatorAttachmentAndRequiredExtensions(
-      authenticatorAttachment: String?,
-      hasClientExtensionResults: Boolean,
-      isDiscoverableCredential: Boolean?,
-      json: JSONObject
-    ) {
-
-      if (authenticatorAttachment != null) {
-        json.put(JSON_KEY_AUTH_ATTACHMENT, authenticatorAttachment)
-      }
-
-      val clientExtensionsJson = JSONObject()
-
-      if (hasClientExtensionResults) {
-        try {
-          if (isDiscoverableCredential != null) {
-            val credPropsObject = JSONObject()
-            credPropsObject.put(JSON_KEY_RK, isDiscoverableCredential)
-            clientExtensionsJson.put(JSON_KEY_CRED_PROPS, credPropsObject)
-          }
-        } catch (t: Throwable) {
-          Log.e(
-            TAG,
-            "ClientExtensionResults faced possible implementation " +
-              "inconsistency in uvmEntries - $t"
-          )
-        }
-      }
-      json.put(JSON_KEY_CLIENT_EXTENSION_RESULTS, clientExtensionsJson)
-    }
-
     fun toAssertPasskeyResponse(cred: SignInCredential): String {
       var json = JSONObject()
       val publicKeyCred = cred.publicKeyCredential
@@ -240,61 +161,24 @@
           )
         }
         is AuthenticatorAssertionResponse -> {
-          beginSignInAssertionResponse(
-            authenticatorResponse.clientDataJSON,
-            authenticatorResponse.authenticatorData,
-            authenticatorResponse.signature,
-            authenticatorResponse.userHandle,
-            json,
-            publicKeyCred.id,
-            publicKeyCred.rawId,
-            publicKeyCred.type,
-            publicKeyCred.authenticatorAttachment,
-            publicKeyCred.clientExtensionResults != null,
-            publicKeyCred.clientExtensionResults?.credProps?.isDiscoverableCredential
-          )
+          try {
+            return publicKeyCred.toJson()
+          } catch (t: Throwable) {
+            throw GetCredentialUnknownException("The PublicKeyCredential response json had " +
+                "an unexpected exception when parsing: ${t.message}")
+          }
         }
         else -> {
           Log.e(
             TAG,
             "AuthenticatorResponse expected assertion response but " +
-              "got: ${authenticatorResponse.javaClass.name}"
+                "got: ${authenticatorResponse.javaClass.name}"
           )
         }
       }
       return json.toString()
     }
 
-    internal fun beginSignInAssertionResponse(
-      clientDataJSON: ByteArray,
-      authenticatorData: ByteArray,
-      signature: ByteArray,
-      userHandle: ByteArray?,
-      json: JSONObject,
-      publicKeyCredId: String,
-      publicKeyCredRawId: ByteArray,
-      publicKeyCredType: String,
-      authenticatorAttachment: String?,
-      hasClientExtensionResults: Boolean,
-      isDiscoverableCredential: Boolean?
-    ) {
-      val responseJson = JSONObject()
-      responseJson.put(JSON_KEY_CLIENT_DATA, b64Encode(clientDataJSON))
-      responseJson.put(JSON_KEY_AUTH_DATA, b64Encode(authenticatorData))
-      responseJson.put(JSON_KEY_SIGNATURE, b64Encode(signature))
-      userHandle?.let { responseJson.put(JSON_KEY_USER_HANDLE, b64Encode(userHandle)) }
-      json.put(JSON_KEY_RESPONSE, responseJson)
-      json.put(JSON_KEY_ID, publicKeyCredId)
-      json.put(JSON_KEY_RAW_ID, b64Encode(publicKeyCredRawId))
-      json.put(JSON_KEY_TYPE, publicKeyCredType)
-      addOptionalAuthenticatorAttachmentAndRequiredExtensions(
-        authenticatorAttachment,
-        hasClientExtensionResults,
-        isDiscoverableCredential,
-        json
-      )
-    }
-
     /**
      * Converts from the Credential Manager public key credential option to the Play Auth Module
      * passkey json option.
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 027bd5a..e9ff045 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -436,6 +436,14 @@
 WARN: Missing @param tag for parameter `previousPageKey` of function androidx\.paging\/PageKeyedDataSource\.LoadInitialCallback\/onResult\/#kotlin\.collections\.List\[TypeParam\(bounds=\[kotlin\.Any\?\]\)\]#kotlin\.Int#kotlin\.Int#TypeParam\(bounds=\[kotlin\.Any\?\]\)\?#TypeParam\(bounds=\[kotlin\.Any\?\]\)\?\/PointingToDeclaration\/
 WARN: Missing @param tag for parameter `priority` of function androidx\.camera\.camera2\.interop\/CaptureRequestOptions\/retrieveOptionWithPriority\/#androidx\.camera\.core\.impl\.Config\.Option<ValueT>#androidx\.camera\.core\.impl\.Config\.OptionPriority\/PointingToDeclaration\/
 WARN: Missing @param tag for parameter `priority` of function androidx\.camera\.core\/CameraXConfig\/retrieveOptionWithPriority\/#androidx\.camera\.core\.impl\.Config\.Option<ValueT>#androidx\.camera\.core\.impl\.Config\.OptionPriority\/PointingToDeclaration\/
+WARN: Missing @param tag for parameter `width` of function androidx\.camera\.testing\.impl\.fakes/FakeImageReaderProxy/newInstance/\#int\#int\#int\#int\#long/PointingToDeclaration/
+WARN: Missing @param tag for parameter `height` of function androidx\.camera\.testing\.impl\.fakes/FakeImageReaderProxy/newInstance/\#int\#int\#int\#int\#long/PointingToDeclaration/
+WARN: Missing @param tag for parameter `format` of function androidx\.camera\.testing\.impl\.fakes/FakeImageReaderProxy/newInstance/\#int\#int\#int\#int\#long/PointingToDeclaration/
+WARN: Missing @param tag for parameter `usage` of function androidx\.camera\.testing\.impl\.fakes/FakeImageReaderProxy/newInstance/\#int\#int\#int\#int\#long/PointingToDeclaration/
+WARN: Missing @param tag for parameter `priority` of function androidx\.camera\.testing\.impl\.fakes/FakeUseCaseConfig/retrieveOptionWithPriority/\#androidx\.camera\.core\.impl\.Config\.Option<ValueT>\#androidx\.camera\.core\.impl\.Config\.OptionPriority/PointingToDeclaration/
+WARN: Missing @param tag for parameter `classType` of function androidx\.camera\.testing\.impl\.mocks/MockConsumer/verifyAcceptCall/\#java\.lang\.Class<\?>\#boolean\#androidx\.camera\.testing\.impl\.mocks\.helpers\.CallTimes\#androidx\.camera\.testing\.impl\.mocks\.helpers\.ArgumentCaptor<T>/PointingToDeclaration/
+WARN: Missing @param tag for parameter `inOrder` of function androidx\.camera\.testing\.impl\.mocks/MockConsumer/verifyAcceptCall/\#java\.lang\.Class<\?>\#boolean\#androidx\.camera\.testing\.impl\.mocks\.helpers\.CallTimes\#androidx\.camera\.testing\.impl\.mocks\.helpers\.ArgumentCaptor<T>/PointingToDeclaration/
+WARN: Missing @param tag for parameter `callTimes` of function androidx\.camera\.testing\.impl\.mocks/MockConsumer/verifyAcceptCall/\#java\.lang\.Class<\?>\#boolean\#androidx\.camera\.testing\.impl\.mocks\.helpers\.CallTimes\#androidx\.camera\.testing\.impl\.mocks\.helpers\.ArgumentCaptor<T>/PointingToDeclaration/
 WARN: Missing @param tag for parameter `query` of function androidx\.leanback\.app\/SearchFragment\/createArgs\/#android\.os\.Bundle#java\.lang\.String\/PointingToDeclaration\/
 WARN: Missing @param tag for parameter `query` of function androidx\.leanback\.app\/SearchSupportFragment\/createArgs\/#android\.os\.Bundle#java\.lang\.String\/PointingToDeclaration\/
 WARN: Missing @param tag for parameter `radians` of function androidx\.compose\.ui\.graphics\/\/rotateRad\/androidx\.compose\.ui\.graphics\.Canvas#kotlin\.Float#kotlin\.Float#kotlin\.Float\/PointingToDeclaration\/
@@ -673,6 +681,8 @@
 public abstract java\.util\.List<androidx\.room\.integration\.kotlintestapp\.test\.JvmNameInDaoTest\.JvmNameEntity> jvmQuery\(\);
 public abstract androidx\.room\.integration\.kotlintestapp\.test\.JvmNameInDaoTest\.JvmNameDao jvmDao\(\);
 \^
+\$SUPPORT/slice/slice\-benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics\.java:[0-9]+: warning: \[deprecation\] SliceView in androidx\.slice\.widget has been deprecated
+import androidx\.slice\.widget\.SliceView;
 # b/296419682
 \$SUPPORT/concurrent/concurrent\-futures/src/test/java/androidx/concurrent/futures/AbstractResolvableFutureTest\.java:[0-9]+: warning: \[removal\] resume\(\) in Thread has been deprecated and marked for removal
 thread\.resume\(\);
@@ -721,4 +731,13 @@
 # b/271306193 remove after aosp/2589888 :emoji:emoji:spdxSbomForRelease
 spdx sboms require a version but project: noto\-emoji\-compat\-flatbuffers has no specified version
 # > Configure project :internal-testutils-appcompat
-WARNING: The option setting 'android\.experimental\.lint\.reservedMemoryPerTask=[0-9]+g' is experimental\.
\ No newline at end of file
+WARNING: The option setting 'android\.experimental\.lint\.reservedMemoryPerTask=[0-9]+g' is experimental\.
+# > Task :slice:slice-builders-ktx:compileDebugKotlin
+w: file://\$SUPPORT/slice/slice\-builders\-ktx/src/main/java/androidx/slice/builders/ListBuilder\.kt:[0-9]+:[0-9]+ 'ListBuilder' is deprecated\. Deprecated in Java
+# > Task :slice:slice-builders-ktx:compileDebugAndroidTestKotlin
+w: file://\$SUPPORT/slice/slice\-builders\-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest\.kt:[0-9]+:[0-9]+ 'SliceProvider' is deprecated\. Deprecated in Java
+w: file://\$SUPPORT/slice/slice\-builders\-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest\.kt:[0-9]+:[0-9]+ 'SliceSpecs' is deprecated\. Deprecated in Java
+w: file://\$SUPPORT/slice/slice\-builders\-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest\.kt:[0-9]+:[0-9]+ 'ListBuilder' is deprecated\. Deprecated in Java
+# > Task :slice:slice-benchmark:compileReleaseAndroidTestJavaWithJavac
+\$SUPPORT/slice/slice\-benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics\.java:[0-9]+: warning: \[deprecation\] SliceHints in androidx\.slice\.core has been deprecated
+import androidx\.slice\.core\.SliceHints;
diff --git a/development/update-verification-metadata.sh b/development/update-verification-metadata.sh
index 7023439..a0487f3 100755
--- a/development/update-verification-metadata.sh
+++ b/development/update-verification-metadata.sh
@@ -1,13 +1,38 @@
 #!/bin/bash
 set -e
 
+# This script updates trust entries in gradle/verification-metadata.xml
+
+# Usage: $0 [--no-dry-run] [<task>]
+
+# --no-dry-run
+#   Don't pass --dry-run to Gradle, so Gradle executes the corresponding tasks.
+#   This is not normally necessary but in some cases can be a useful workaround.
+#   When https://github.com/gradle/gradle/issues/26289 is resolved, we should reevaluate this behavior
+#
+# <task>
+#   The task to ask Gradle to run. By default this is 'bOS'
+#   When --no-dry-run is removed, we should reevaluate this behavior
+
+dryrun=true
+task="bOS"
+
+while [ "$1" != "" ]; do
+  arg="$1"
+  shift
+  if [ "$arg" == "--no-dry-run" ]; then
+    dryrun=false
+    continue
+  fi
+  task="$arg"
+done
+
 function runGradle() {
-  kmpArgs="-Pandroidx.enabled.kmp.target.platforms=+native"
-  echo running ./gradlew $kmpArgs "$@"
-  if ./gradlew $kmpArgs "$@"; then
-    echo succeeded: ./gradlew $kmpArgs "$@"
+  echo running ./gradlew "$@"
+  if ./gradlew "$@"; then
+    echo succeeded: ./gradlew "$@"
   else
-    echo failed: ./gradlew $kmpArgs "$@"
+    echo failed: ./gradlew "$@"
     return 1
   fi
 }
@@ -18,20 +43,31 @@
   # regenerate metadata
   # Need to run a clean build, https://github.com/gradle/gradle/issues/19228
   # Resolving Configurations before task execution is expected. b/297394547
-  runGradle --stacktrace --write-verification-metadata pgp,sha256 --export-keys --dry-run --clean -Pandroidx.update.signatures=true -Pandroid.dependencyResolutionAtConfigurationTime.disallow=false bOS
+  dryrunArg=""
+  if [ "$dryrun" == "true" ]; then
+    dryrunArg="--dry-run"
+  fi
+  runGradle --stacktrace --write-verification-metadata pgp,sha256 --export-keys $dryrunArg --clean -Pandroidx.update.signatures=true -Pandroid.dependencyResolutionAtConfigurationTime.disallow=false -Pandroidx.enabled.kmp.target.platforms=+native $task
 
   # update verification metadata file
-  # also remove 'version=' lines, https://github.com/gradle/gradle/issues/20192
-  cat gradle/verification-metadata.dryrun.xml | sed 's/ \(trusted-key.*\)version="[^"]*"/\1/' > gradle/verification-metadata.xml
+
+  # first, make sure the resulting file is named "verification-metadata.xml"
+  if [ "$dryrun" == "true" ]; then
+    mv gradle/verification-metadata.dryrun.xml gradle/verification-metadata.xml
+  fi
+
+  # next, remove 'version=' lines https://github.com/gradle/gradle/issues/20192
+  sed -i 's/\(trusted-key.*\)version="[^"]*"/\1/' gradle/verification-metadata.xml
 
   # rename keyring
-  mv gradle/verification-keyring-dryrun.keys gradle/verification-keyring.keys
+  mv gradle/verification-keyring-dryrun.keys gradle/verification-keyring.keys 2>/dev/null || true
 
   # remove temporary files
   rm -f gradle/verification-keyring-dryrun.gpg
-  rm -f gradle/verification-metadata.dryrun.xml
+  rm -f gradle/verification-keyring.gpg
 }
 regenerateVerificationMetadata
 
 echo
 echo 'Done. Please check that these changes look correct (`git diff`)'
+echo "If Gradle did not make all expected updates to verification-metadata.xml, you can try '--no-dry-run'. This is slow so you may also want to specify a task. Example: $0 --dry-run exportSboms"
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 1c2c888..2954cc7 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -1,5 +1,5 @@
 plugins {
-    id("com.android.library")
+id("com.android.library")
     id("AndroidXDocsPlugin")
 }
 
@@ -8,15 +8,15 @@
 }
 
 dependencies {
-    docs("androidx.activity:activity:1.8.0-alpha07")
-    docs("androidx.activity:activity-compose:1.8.0-alpha07")
-    samples("androidx.activity:activity-compose-samples:1.8.0-alpha07")
-    docs("androidx.activity:activity-ktx:1.8.0-alpha07")
+    docs("androidx.activity:activity:1.8.0-beta01")
+    docs("androidx.activity:activity-compose:1.8.0-beta01")
+    samples("androidx.activity:activity-compose-samples:1.8.0-beta01")
+    docs("androidx.activity:activity-ktx:1.8.0-beta01")
     // ads-identifier is deprecated
     docsWithoutApiSince("androidx.ads:ads-identifier:1.0.0-alpha05")
     docsWithoutApiSince("androidx.ads:ads-identifier-common:1.0.0-alpha05")
     docsWithoutApiSince("androidx.ads:ads-identifier-provider:1.0.0-alpha05")
-    kmpDocs("androidx.annotation:annotation:1.7.0-rc01")
+    kmpDocs("androidx.annotation:annotation:1.7.0")
     docs("androidx.annotation:annotation-experimental:1.4.0-alpha01")
     docs("androidx.appcompat:appcompat:1.7.0-alpha03")
     docs("androidx.appcompat:appcompat-resources:1.7.0-alpha03")
@@ -32,10 +32,10 @@
     docs("androidx.asynclayoutinflater:asynclayoutinflater:1.1.0-alpha01")
     docs("androidx.asynclayoutinflater:asynclayoutinflater-appcompat:1.1.0-alpha01")
     docs("androidx.autofill:autofill:1.3.0-alpha01")
-    docs("androidx.benchmark:benchmark-common:1.2.0-beta04")
-    docs("androidx.benchmark:benchmark-junit4:1.2.0-beta04")
-    docs("androidx.benchmark:benchmark-macro:1.2.0-beta04")
-    docs("androidx.benchmark:benchmark-macro-junit4:1.2.0-beta04")
+    docs("androidx.benchmark:benchmark-common:1.2.0-beta05")
+    docs("androidx.benchmark:benchmark-junit4:1.2.0-beta05")
+    docs("androidx.benchmark:benchmark-macro:1.2.0-beta05")
+    docs("androidx.benchmark:benchmark-macro-junit4:1.2.0-beta05")
     docs("androidx.biometric:biometric:1.2.0-alpha05")
     docs("androidx.biometric:biometric-ktx:1.2.0-alpha05")
     samples("androidx.biometric:biometric-ktx-samples:1.2.0-alpha05")
@@ -58,57 +58,57 @@
     docs("androidx.car.app:app-projected:1.4.0-beta01")
     docs("androidx.car.app:app-testing:1.4.0-beta01")
     docs("androidx.cardview:cardview:1.0.0")
-    kmpDocs("androidx.collection:collection:1.3.0-beta01")
-    docs("androidx.collection:collection-ktx:1.3.0-beta01")
-    kmpDocs("androidx.compose.animation:animation:1.6.0-alpha04")
-    kmpDocs("androidx.compose.animation:animation-core:1.6.0-alpha04")
-    kmpDocs("androidx.compose.animation:animation-graphics:1.6.0-alpha04")
-    samples("androidx.compose.animation:animation-samples:1.6.0-alpha04")
-    samples("androidx.compose.animation:animation-core-samples:1.6.0-alpha04")
-    samples("androidx.compose.animation:animation-graphics-samples:1.6.0-alpha04")
-    kmpDocs("androidx.compose.foundation:foundation:1.6.0-alpha04")
-    kmpDocs("androidx.compose.foundation:foundation-layout:1.6.0-alpha04")
-    samples("androidx.compose.foundation:foundation-layout-samples:1.6.0-alpha04")
-    samples("androidx.compose.foundation:foundation-samples:1.6.0-alpha04")
-    kmpDocs("androidx.compose.material3:material3:1.2.0-alpha06")
-    samples("androidx.compose.material3:material3-samples:1.2.0-alpha06")
-    kmpDocs("androidx.compose.material3:material3-window-size-class:1.2.0-alpha06")
-    samples("androidx.compose.material3:material3-window-size-class-samples:1.2.0-alpha06")
-    kmpDocs("androidx.compose.material:material:1.6.0-alpha04")
-    kmpDocs("androidx.compose.material:material-icons-core:1.6.0-alpha04")
-    samples("androidx.compose.material:material-icons-core-samples:1.6.0-alpha04")
-    kmpDocs("androidx.compose.material:material-ripple:1.6.0-alpha04")
-    samples("androidx.compose.material:material-samples:1.6.0-alpha04")
-    kmpDocs("androidx.compose.runtime:runtime:1.6.0-alpha04")
-    docs("androidx.compose.runtime:runtime-livedata:1.6.0-alpha04")
-    samples("androidx.compose.runtime:runtime-livedata-samples:1.6.0-alpha04")
-    docs("androidx.compose.runtime:runtime-rxjava2:1.6.0-alpha04")
-    samples("androidx.compose.runtime:runtime-rxjava2-samples:1.6.0-alpha04")
-    docs("androidx.compose.runtime:runtime-rxjava3:1.6.0-alpha04")
-    samples("androidx.compose.runtime:runtime-rxjava3-samples:1.6.0-alpha04")
-    kmpDocs("androidx.compose.runtime:runtime-saveable:1.6.0-alpha04")
-    samples("androidx.compose.runtime:runtime-saveable-samples:1.6.0-alpha04")
-    samples("androidx.compose.runtime:runtime-samples:1.6.0-alpha04")
-    docs("androidx.compose.runtime:runtime-tracing:1.0.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui:1.6.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-geometry:1.6.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-graphics:1.6.0-alpha04")
-    samples("androidx.compose.ui:ui-graphics-samples:1.6.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-test:1.6.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-test-junit4:1.6.0-alpha04")
-    samples("androidx.compose.ui:ui-test-samples:1.6.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-text:1.6.0-alpha04")
-    docs("androidx.compose.ui:ui-text-google-fonts:1.6.0-alpha04")
-    samples("androidx.compose.ui:ui-text-samples:1.6.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-tooling:1.6.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-tooling-data:1.6.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-tooling-preview:1.6.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-unit:1.6.0-alpha04")
-    samples("androidx.compose.ui:ui-unit-samples:1.6.0-alpha04")
-    kmpDocs("androidx.compose.ui:ui-util:1.6.0-alpha04")
-    docs("androidx.compose.ui:ui-viewbinding:1.6.0-alpha04")
-    samples("androidx.compose.ui:ui-viewbinding-samples:1.6.0-alpha04")
-    samples("androidx.compose.ui:ui-samples:1.6.0-alpha04")
+    kmpDocs("androidx.collection:collection:1.3.0-rc01")
+    docs("androidx.collection:collection-ktx:1.3.0-rc01")
+    kmpDocs("androidx.compose.animation:animation:1.6.0-alpha05")
+    kmpDocs("androidx.compose.animation:animation-core:1.6.0-alpha05")
+    kmpDocs("androidx.compose.animation:animation-graphics:1.6.0-alpha05")
+    samples("androidx.compose.animation:animation-samples:1.6.0-alpha05")
+    samples("androidx.compose.animation:animation-core-samples:1.6.0-alpha05")
+    samples("androidx.compose.animation:animation-graphics-samples:1.6.0-alpha05")
+    kmpDocs("androidx.compose.foundation:foundation:1.6.0-alpha05")
+    kmpDocs("androidx.compose.foundation:foundation-layout:1.6.0-alpha05")
+    samples("androidx.compose.foundation:foundation-layout-samples:1.6.0-alpha05")
+    samples("androidx.compose.foundation:foundation-samples:1.6.0-alpha05")
+    kmpDocs("androidx.compose.material3:material3:1.2.0-alpha07")
+    samples("androidx.compose.material3:material3-samples:1.2.0-alpha07")
+    kmpDocs("androidx.compose.material3:material3-window-size-class:1.2.0-alpha07")
+    samples("androidx.compose.material3:material3-window-size-class-samples:1.2.0-alpha07")
+    kmpDocs("androidx.compose.material:material:1.6.0-alpha05")
+    kmpDocs("androidx.compose.material:material-icons-core:1.6.0-alpha05")
+    samples("androidx.compose.material:material-icons-core-samples:1.6.0-alpha05")
+    kmpDocs("androidx.compose.material:material-ripple:1.6.0-alpha05")
+    samples("androidx.compose.material:material-samples:1.6.0-alpha05")
+    kmpDocs("androidx.compose.runtime:runtime:1.6.0-alpha05")
+    docs("androidx.compose.runtime:runtime-livedata:1.6.0-alpha05")
+    samples("androidx.compose.runtime:runtime-livedata-samples:1.6.0-alpha05")
+    docs("androidx.compose.runtime:runtime-rxjava2:1.6.0-alpha05")
+    samples("androidx.compose.runtime:runtime-rxjava2-samples:1.6.0-alpha05")
+    docs("androidx.compose.runtime:runtime-rxjava3:1.6.0-alpha05")
+    samples("androidx.compose.runtime:runtime-rxjava3-samples:1.6.0-alpha05")
+    kmpDocs("androidx.compose.runtime:runtime-saveable:1.6.0-alpha05")
+    samples("androidx.compose.runtime:runtime-saveable-samples:1.6.0-alpha05")
+    samples("androidx.compose.runtime:runtime-samples:1.6.0-alpha05")
+    docs("androidx.compose.runtime:runtime-tracing:1.0.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui:1.6.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-geometry:1.6.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-graphics:1.6.0-alpha05")
+    samples("androidx.compose.ui:ui-graphics-samples:1.6.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-test:1.6.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-test-junit4:1.6.0-alpha05")
+    samples("androidx.compose.ui:ui-test-samples:1.6.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-text:1.6.0-alpha05")
+    docs("androidx.compose.ui:ui-text-google-fonts:1.6.0-alpha05")
+    samples("androidx.compose.ui:ui-text-samples:1.6.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-tooling:1.6.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-tooling-data:1.6.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-tooling-preview:1.6.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-unit:1.6.0-alpha05")
+    samples("androidx.compose.ui:ui-unit-samples:1.6.0-alpha05")
+    kmpDocs("androidx.compose.ui:ui-util:1.6.0-alpha05")
+    docs("androidx.compose.ui:ui-viewbinding:1.6.0-alpha05")
+    samples("androidx.compose.ui:ui-viewbinding-samples:1.6.0-alpha05")
+    samples("androidx.compose.ui:ui-samples:1.6.0-alpha05")
     docs("androidx.concurrent:concurrent-futures:1.2.0-alpha02")
     docs("androidx.concurrent:concurrent-futures-ktx:1.2.0-alpha02")
     docs("androidx.constraintlayout:constraintlayout:2.2.0-alpha12")
@@ -124,13 +124,13 @@
     docs("androidx.core:core-i18n:1.0.0-alpha01")
     docs("androidx.core:core-ktx:1.12.0-rc01")
     docs("androidx.core:core-location-altitude:1.0.0-alpha01")
-    docs("androidx.core:core-performance:1.0.0-alpha03")
-    docs("androidx.core:core-performance-play-services:1.0.0-alpha03")
-    samples("androidx.core:core-performance-samples:1.0.0-alpha03")
-    docs("androidx.core:core-performance-testing:1.0.0-alpha03")
+    docs("androidx.core:core-performance:1.0.0-beta01")
+    docs("androidx.core:core-performance-play-services:1.0.0-beta01")
+    samples("androidx.core:core-performance-samples:1.0.0-beta01")
+    docs("androidx.core:core-performance-testing:1.0.0-beta01")
     docs("androidx.core:core-remoteviews:1.0.0-rc01")
     docs("androidx.core:core-role:1.2.0-alpha01")
-    docs("androidx.core:core-splashscreen:1.1.0-alpha01")
+    docs("androidx.core:core-splashscreen:1.1.0-alpha02")
     docs("androidx.core:core-telecom:1.0.0-alpha01")
     docs("androidx.core:core-testing:1.12.0-rc01")
     docs("androidx.core.uwb:uwb:1.0.0-alpha07")
@@ -142,15 +142,15 @@
     docs("androidx.customview:customview:1.2.0-alpha02")
     // TODO(b/294531403): Turn on apiSince for customview-poolingcontainer when it releases as alpha
     docsWithoutApiSince("androidx.customview:customview-poolingcontainer:1.0.0-rc01")
-    kmpDocs("androidx.datastore:datastore:1.1.0-alpha04")
-    kmpDocs("androidx.datastore:datastore-core:1.1.0-alpha04")
-    kmpDocs("androidx.datastore:datastore-core-okio:1.1.0-alpha04")
-    kmpDocs("androidx.datastore:datastore-preferences:1.1.0-alpha04")
-    kmpDocs("androidx.datastore:datastore-preferences-core:1.1.0-alpha04")
-    docs("androidx.datastore:datastore-preferences-rxjava2:1.1.0-alpha04")
-    docs("androidx.datastore:datastore-preferences-rxjava3:1.1.0-alpha04")
-    docs("androidx.datastore:datastore-rxjava2:1.1.0-alpha04")
-    docs("androidx.datastore:datastore-rxjava3:1.1.0-alpha04")
+    kmpDocs("androidx.datastore:datastore:1.1.0-alpha05")
+    kmpDocs("androidx.datastore:datastore-core:1.1.0-alpha05")
+    kmpDocs("androidx.datastore:datastore-core-okio:1.1.0-alpha05")
+    kmpDocs("androidx.datastore:datastore-preferences:1.1.0-alpha05")
+    kmpDocs("androidx.datastore:datastore-preferences-core:1.1.0-alpha05")
+    docs("androidx.datastore:datastore-preferences-rxjava2:1.1.0-alpha05")
+    docs("androidx.datastore:datastore-preferences-rxjava3:1.1.0-alpha05")
+    docs("androidx.datastore:datastore-rxjava2:1.1.0-alpha05")
+    docs("androidx.datastore:datastore-rxjava3:1.1.0-alpha05")
     docs("androidx.documentfile:documentfile:1.1.0-alpha01")
     docs("androidx.draganddrop:draganddrop:1.0.0")
     docs("androidx.drawerlayout:drawerlayout:1.2.0")
@@ -167,23 +167,24 @@
     docs("androidx.enterprise:enterprise-feedback:1.1.0")
     docs("androidx.enterprise:enterprise-feedback-testing:1.1.0")
     docs("androidx.exifinterface:exifinterface:1.3.6")
-    docs("androidx.fragment:fragment:1.7.0-alpha03")
-    docs("androidx.fragment:fragment-ktx:1.7.0-alpha03")
-    docs("androidx.fragment:fragment-testing:1.7.0-alpha03")
-    docs("androidx.glance:glance:1.0.0-rc01")
-    docs("androidx.glance:glance-appwidget:1.0.0-rc01")
-    samples("androidx.glance:glance-appwidget-samples:1.0.0-rc01")
+    docs("androidx.fragment:fragment:1.7.0-alpha04")
+    docs("androidx.fragment:fragment-ktx:1.7.0-alpha04")
+    docs("androidx.fragment:fragment-testing:1.7.0-alpha04")
+    docs("androidx.glance:glance:1.0.0")
+    docs("androidx.glance:glance-appwidget:1.0.0")
+    samples("androidx.glance:glance-appwidget-samples:1.0.0")
     docs("androidx.glance:glance-appwidget-preview:1.0.0-alpha06")
-    docs("androidx.glance:glance-material:1.0.0-rc01")
-    docs("androidx.glance:glance-material3:1.0.0-rc01")
+    docs("androidx.glance:glance-material:1.0.0")
+    docs("androidx.glance:glance-material3:1.0.0")
     docs("androidx.glance:glance-preview:1.0.0-alpha06")
     docs("androidx.glance:glance-template:1.0.0-alpha06")
     docs("androidx.glance:glance-wear-tiles:1.0.0-alpha06")
     docs("androidx.glance:glance-wear-tiles-preview:1.0.0-alpha06")
-    docs("androidx.graphics:graphics-core:1.0.0-alpha04")
+    docs("androidx.graphics:graphics-core:1.0.0-alpha05")
+    samples("androidx.graphics:graphics-core-samples:1.0.0-alpha05")
     docs("androidx.gridlayout:gridlayout:1.1.0-beta01")
-    docs("androidx.health.connect:connect-client:1.1.0-alpha03")
-    samples("androidx.health.connect:connect-client-samples:1.1.0-alpha03")
+    docs("androidx.health.connect:connect-client:1.1.0-alpha04")
+    samples("androidx.health.connect:connect-client-samples:1.1.0-alpha04")
     docs("androidx.health:health-services-client:1.1.0-alpha01")
     docs("androidx.heifwriter:heifwriter:1.1.0-alpha02")
     docs("androidx.hilt:hilt-common:1.1.0-alpha01")
@@ -195,32 +196,32 @@
     docs("androidx.input:input-motionprediction:1.0.0-beta02")
     docs("androidx.interpolator:interpolator:1.0.0")
     docs("androidx.javascriptengine:javascriptengine:1.0.0-alpha05")
-    docs("androidx.leanback:leanback:1.2.0-alpha02")
-    docs("androidx.leanback:leanback-grid:1.0.0-alpha01")
-    docs("androidx.leanback:leanback-paging:1.1.0-alpha09")
-    docs("androidx.leanback:leanback-preference:1.2.0-alpha02")
+    docs("androidx.leanback:leanback:1.2.0-alpha03")
+    docs("androidx.leanback:leanback-grid:1.0.0-alpha02")
+    docs("androidx.leanback:leanback-paging:1.1.0-alpha10")
+    docs("androidx.leanback:leanback-preference:1.2.0-alpha03")
     docs("androidx.leanback:leanback-tab:1.1.0-beta01")
-    docs("androidx.lifecycle:lifecycle-common:2.7.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-common-java8:2.7.0-alpha01")
+    docs("androidx.lifecycle:lifecycle-common:2.7.0-alpha02")
+    docs("androidx.lifecycle:lifecycle-common-java8:2.7.0-alpha02")
     docs("androidx.lifecycle:lifecycle-extensions:2.2.0")
-    docs("androidx.lifecycle:lifecycle-livedata:2.7.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-livedata-core:2.7.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-livedata-core-ktx:2.7.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-process:2.7.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-reactivestreams:2.7.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-reactivestreams-ktx:2.7.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-runtime:2.7.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-runtime-compose:2.7.0-alpha01")
-    samples("androidx.lifecycle:lifecycle-runtime-compose-samples:2.7.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-runtime-testing:2.7.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-service:2.7.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-viewmodel:2.7.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0-alpha01")
-    samples("androidx.lifecycle:lifecycle-viewmodel-compose-samples:2.7.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0-alpha01")
-    docs("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0-alpha01")
+    docs("androidx.lifecycle:lifecycle-livedata:2.7.0-alpha02")
+    docs("androidx.lifecycle:lifecycle-livedata-core:2.7.0-alpha02")
+    docs("androidx.lifecycle:lifecycle-livedata-core-ktx:2.7.0-alpha02")
+    docs("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0-alpha02")
+    docs("androidx.lifecycle:lifecycle-process:2.7.0-alpha02")
+    docs("androidx.lifecycle:lifecycle-reactivestreams:2.7.0-alpha02")
+    docs("androidx.lifecycle:lifecycle-reactivestreams-ktx:2.7.0-alpha02")
+    docs("androidx.lifecycle:lifecycle-runtime:2.7.0-alpha02")
+    docs("androidx.lifecycle:lifecycle-runtime-compose:2.7.0-alpha02")
+    samples("androidx.lifecycle:lifecycle-runtime-compose-samples:2.7.0-alpha02")
+    docs("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0-alpha02")
+    docs("androidx.lifecycle:lifecycle-runtime-testing:2.7.0-alpha02")
+    docs("androidx.lifecycle:lifecycle-service:2.7.0-alpha02")
+    docs("androidx.lifecycle:lifecycle-viewmodel:2.7.0-alpha02")
+    docs("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0-alpha02")
+    samples("androidx.lifecycle:lifecycle-viewmodel-compose-samples:2.7.0-alpha02")
+    docs("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0-alpha02")
+    docs("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0-alpha02")
     docs("androidx.loader:loader:1.1.0")
     // localbroadcastmanager is deprecated
     docsWithoutApiSince("androidx.localbroadcastmanager:localbroadcastmanager:1.1.0")
@@ -258,31 +259,31 @@
     docs("androidx.mediarouter:mediarouter:1.6.0-rc01")
     docs("androidx.mediarouter:mediarouter-testing:1.6.0-rc01")
     docs("androidx.metrics:metrics-performance:1.0.0-alpha04")
-    docs("androidx.navigation:navigation-common:2.7.1")
-    docs("androidx.navigation:navigation-common-ktx:2.7.1")
-    docs("androidx.navigation:navigation-compose:2.7.1")
-    samples("androidx.navigation:navigation-compose-samples:2.7.1")
-    docs("androidx.navigation:navigation-dynamic-features-fragment:2.7.1")
-    docs("androidx.navigation:navigation-dynamic-features-runtime:2.7.1")
-    docs("androidx.navigation:navigation-fragment:2.7.1")
-    docs("androidx.navigation:navigation-fragment-ktx:2.7.1")
-    docs("androidx.navigation:navigation-runtime:2.7.1")
-    docs("androidx.navigation:navigation-runtime-ktx:2.7.1")
-    docs("androidx.navigation:navigation-testing:2.7.1")
-    docs("androidx.navigation:navigation-ui:2.7.1")
-    docs("androidx.navigation:navigation-ui-ktx:2.7.1")
-    docs("androidx.paging:paging-common:3.2.0")
-    docs("androidx.paging:paging-common-ktx:3.2.0")
-    docs("androidx.paging:paging-compose:3.2.0")
-    samples("androidx.paging:paging-compose-samples:3.2.0")
-    docs("androidx.paging:paging-guava:3.2.0")
-    docs("androidx.paging:paging-runtime:3.2.0")
-    docs("androidx.paging:paging-runtime-ktx:3.2.0")
-    docs("androidx.paging:paging-rxjava2:3.2.0")
-    docs("androidx.paging:paging-rxjava2-ktx:3.2.0")
-    docs("androidx.paging:paging-rxjava3:3.2.0")
-    samples("androidx.paging:paging-samples:3.2.0")
-    docs("androidx.paging:paging-testing:3.2.0")
+    docs("androidx.navigation:navigation-common:2.7.2")
+    docs("androidx.navigation:navigation-common-ktx:2.7.2")
+    docs("androidx.navigation:navigation-compose:2.7.2")
+    samples("androidx.navigation:navigation-compose-samples:2.7.2")
+    docs("androidx.navigation:navigation-dynamic-features-fragment:2.7.2")
+    docs("androidx.navigation:navigation-dynamic-features-runtime:2.7.2")
+    docs("androidx.navigation:navigation-fragment:2.7.2")
+    docs("androidx.navigation:navigation-fragment-ktx:2.7.2")
+    docs("androidx.navigation:navigation-runtime:2.7.2")
+    docs("androidx.navigation:navigation-runtime-ktx:2.7.2")
+    docs("androidx.navigation:navigation-testing:2.7.2")
+    docs("androidx.navigation:navigation-ui:2.7.2")
+    docs("androidx.navigation:navigation-ui-ktx:2.7.2")
+    docsWithoutApiSince("androidx.paging:paging-common:3.2.1")
+    docsWithoutApiSince("androidx.paging:paging-common-ktx:3.2.1")
+    docsWithoutApiSince("androidx.paging:paging-compose:3.2.1")
+    samples("androidx.paging:paging-compose-samples:3.2.1")
+    docsWithoutApiSince("androidx.paging:paging-guava:3.2.1")
+    docsWithoutApiSince("androidx.paging:paging-runtime:3.2.1")
+    docsWithoutApiSince("androidx.paging:paging-runtime-ktx:3.2.1")
+    docsWithoutApiSince("androidx.paging:paging-rxjava2:3.2.1")
+    docsWithoutApiSince("androidx.paging:paging-rxjava2-ktx:3.2.1")
+    docsWithoutApiSince("androidx.paging:paging-rxjava3:3.2.1")
+    samples("androidx.paging:paging-samples:3.2.1")
+    docsWithoutApiSince("androidx.paging:paging-testing:3.2.1")
     docs("androidx.palette:palette:1.0.0")
     docs("androidx.palette:palette-ktx:1.0.0")
     docs("androidx.percentlayout:percentlayout:1.0.1")
@@ -293,7 +294,7 @@
     docs("androidx.privacysandbox.ads:ads-adservices-java:1.1.0-beta01")
     docs("androidx.privacysandbox.sdkruntime:sdkruntime-client:1.0.0-alpha08")
     docs("androidx.privacysandbox.sdkruntime:sdkruntime-core:1.0.0-alpha08")
-    docs("androidx.privacysandbox.tools:tools:1.0.0-alpha05")
+    docs("androidx.privacysandbox.tools:tools:1.0.0-alpha06")
     docs("androidx.privacysandbox.ui:ui-client:1.0.0-alpha05")
     docs("androidx.privacysandbox.ui:ui-core:1.0.0-alpha05")
     docs("androidx.privacysandbox.ui:ui-provider:1.0.0-alpha05")
@@ -358,15 +359,15 @@
     docsWithoutApiSince("androidx.textclassifier:textclassifier:1.0.0-alpha04")
     docs("androidx.tracing:tracing:1.3.0-alpha02")
     docs("androidx.tracing:tracing-ktx:1.3.0-alpha02")
-    docs("androidx.tracing:tracing-perfetto:1.0.0-beta02")
+    docs("androidx.tracing:tracing-perfetto:1.0.0-beta03")
     // TODO(243405142) clean-up
     docsWithoutApiSince("androidx.tracing:tracing-perfetto-common:1.0.0-alpha16")
-    docs("androidx.tracing:tracing-perfetto-handshake:1.0.0-beta02")
-    docs("androidx.transition:transition:1.5.0-alpha01")
-    docs("androidx.transition:transition-ktx:1.5.0-alpha01")
-    docs("androidx.tv:tv-foundation:1.0.0-alpha08")
-    docs("androidx.tv:tv-material:1.0.0-alpha08")
-    samples("androidx.tv:tv-samples:1.0.0-alpha08")
+    docs("androidx.tracing:tracing-perfetto-handshake:1.0.0-beta03")
+    docs("androidx.transition:transition:1.5.0-alpha02")
+    docs("androidx.transition:transition-ktx:1.5.0-alpha02")
+    docs("androidx.tv:tv-foundation:1.0.0-alpha09")
+    docs("androidx.tv:tv-material:1.0.0-alpha09")
+    samples("androidx.tv:tv-samples:1.0.0-alpha09")
     docs("androidx.tvprovider:tvprovider:1.1.0-alpha01")
     docs("androidx.vectordrawable:vectordrawable:1.2.0-beta01")
     docs("androidx.vectordrawable:vectordrawable-animated:1.2.0-alpha01")
@@ -374,16 +375,16 @@
     docs("androidx.versionedparcelable:versionedparcelable:1.1.1")
     docs("androidx.viewpager2:viewpager2:1.1.0-beta02")
     docs("androidx.viewpager:viewpager:1.1.0-alpha01")
-    docs("androidx.wear.compose:compose-foundation:1.3.0-alpha04")
-    samples("androidx.wear.compose:compose-foundation-samples:1.3.0-alpha04")
-    docs("androidx.wear.compose:compose-material:1.3.0-alpha04")
-    docs("androidx.wear.compose:compose-material-core:1.3.0-alpha04")
-    samples("androidx.wear.compose:compose-material-samples:1.3.0-alpha04")
-    docs("androidx.wear.compose:compose-material3:1.0.0-alpha10")
-    samples("androidx.wear.compose:compose-material3-samples:1.3.0-alpha04")
-    docs("androidx.wear.compose:compose-navigation:1.3.0-alpha04")
-    samples("androidx.wear.compose:compose-navigation-samples:1.3.0-alpha04")
-    docs("androidx.wear.compose:compose-ui-tooling:1.3.0-alpha04")
+    docs("androidx.wear.compose:compose-foundation:1.3.0-alpha05")
+    samples("androidx.wear.compose:compose-foundation-samples:1.3.0-alpha05")
+    docs("androidx.wear.compose:compose-material:1.3.0-alpha05")
+    docs("androidx.wear.compose:compose-material-core:1.3.0-alpha05")
+    samples("androidx.wear.compose:compose-material-samples:1.3.0-alpha05")
+    docs("androidx.wear.compose:compose-material3:1.0.0-alpha11")
+    samples("androidx.wear.compose:compose-material3-samples:1.3.0-alpha05")
+    docs("androidx.wear.compose:compose-navigation:1.3.0-alpha05")
+    samples("androidx.wear.compose:compose-navigation-samples:1.3.0-alpha05")
+    docs("androidx.wear.compose:compose-ui-tooling:1.3.0-alpha05")
     docs("androidx.wear.protolayout:protolayout:1.0.0")
     docs("androidx.wear.protolayout:protolayout-expression:1.0.0")
     docs("androidx.wear.protolayout:protolayout-expression-pipeline:1.0.0")
@@ -394,22 +395,22 @@
     docs("androidx.wear.tiles:tiles-renderer:1.2.0")
     docs("androidx.wear.tiles:tiles-testing:1.2.0")
     docs("androidx.wear.tiles:tiles-tooling:1.2.0-alpha07")
-    docs("androidx.wear.watchface:watchface:1.2.0-beta01")
-    docs("androidx.wear.watchface:watchface-client:1.2.0-beta01")
-    docs("androidx.wear.watchface:watchface-client-guava:1.2.0-beta01")
-    docs("androidx.wear.watchface:watchface-complications:1.2.0-beta01")
-    docs("androidx.wear.watchface:watchface-complications-data:1.2.0-beta01")
-    docs("androidx.wear.watchface:watchface-complications-data-source:1.2.0-beta01")
-    docs("androidx.wear.watchface:watchface-complications-data-source-ktx:1.2.0-beta01")
-    samples("androidx.wear.watchface:watchface-complications-permission-dialogs-sample:1.2.0-beta01")
-    docs("androidx.wear.watchface:watchface-complications-rendering:1.2.0-beta01")
-    docs("androidx.wear.watchface:watchface-data:1.2.0-beta01")
-    docs("androidx.wear.watchface:watchface-editor:1.2.0-beta01")
-    docs("androidx.wear.watchface:watchface-editor-guava:1.2.0-beta01")
-    samples("androidx.wear.watchface:watchface-editor-samples:1.2.0-beta01")
-    docs("androidx.wear.watchface:watchface-guava:1.2.0-beta01")
-    samples("androidx.wear.watchface:watchface-samples:1.2.0-beta01")
-    docs("androidx.wear.watchface:watchface-style:1.2.0-beta01")
+    docs("androidx.wear.watchface:watchface:1.2.0-beta02")
+    docs("androidx.wear.watchface:watchface-client:1.2.0-beta02")
+    docs("androidx.wear.watchface:watchface-client-guava:1.2.0-beta02")
+    docs("androidx.wear.watchface:watchface-complications:1.2.0-beta02")
+    docs("androidx.wear.watchface:watchface-complications-data:1.2.0-beta02")
+    docs("androidx.wear.watchface:watchface-complications-data-source:1.2.0-beta02")
+    docs("androidx.wear.watchface:watchface-complications-data-source-ktx:1.2.0-beta02")
+    samples("androidx.wear.watchface:watchface-complications-permission-dialogs-sample:1.2.0-beta02")
+    docs("androidx.wear.watchface:watchface-complications-rendering:1.2.0-beta02")
+    docs("androidx.wear.watchface:watchface-data:1.2.0-beta02")
+    docs("androidx.wear.watchface:watchface-editor:1.2.0-beta02")
+    docs("androidx.wear.watchface:watchface-editor-guava:1.2.0-beta02")
+    samples("androidx.wear.watchface:watchface-editor-samples:1.2.0-beta02")
+    docs("androidx.wear.watchface:watchface-guava:1.2.0-beta02")
+    samples("androidx.wear.watchface:watchface-samples:1.2.0-beta02")
+    docs("androidx.wear.watchface:watchface-style:1.2.0-beta02")
     // TODO(b/294531403): Turn on apiSince for wear when it releases as alpha
     docsWithoutApiSince("androidx.wear:wear:1.3.0-rc01")
     stubs(fileTree(dir: "../wear/wear_stubs/", include: ["com.google.android.wearable-stubs.jar"]))
@@ -420,7 +421,7 @@
     docs("androidx.wear:wear-phone-interactions:1.1.0-alpha03")
     docs("androidx.wear:wear-remote-interactions:1.1.0-alpha01")
     docs("androidx.wear:wear-tooling-preview:1.0.0-alpha01")
-    docs("androidx.webkit:webkit:1.8.0-rc01")
+    docs("androidx.webkit:webkit:1.8.0")
     docs("androidx.window.extensions.core:core:1.0.0")
     docs("androidx.window:window:1.2.0-beta01")
     stubs(fileTree(dir: "../window/stubs/", include: ["window-sidecar-release-0.1.0-alpha01.aar"]))
@@ -431,12 +432,12 @@
     docs("androidx.window:window-rxjava3:1.2.0-beta01")
     samples("androidx.window:window-samples:1.2.0-beta01")
     docs("androidx.window:window-testing:1.2.0-beta01")
-    docs("androidx.work:work-gcm:2.9.0-alpha02")
-    docs("androidx.work:work-multiprocess:2.9.0-alpha02")
-    docs("androidx.work:work-runtime:2.9.0-alpha02")
-    docs("androidx.work:work-runtime-ktx:2.9.0-alpha02")
-    docs("androidx.work:work-rxjava2:2.9.0-alpha02")
-    docs("androidx.work:work-rxjava3:2.9.0-alpha02")
-    docs("androidx.work:work-testing:2.9.0-alpha02")
+    docs("androidx.work:work-gcm:2.9.0-beta01")
+    docs("androidx.work:work-multiprocess:2.9.0-beta01")
+    docs("androidx.work:work-runtime:2.9.0-beta01")
+    docs("androidx.work:work-runtime-ktx:2.9.0-beta01")
+    docs("androidx.work:work-rxjava2:2.9.0-beta01")
+    docs("androidx.work:work-rxjava3:2.9.0-beta01")
+    docs("androidx.work:work-testing:2.9.0-beta01")
 }
 
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index d65c0f9..2af5022 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -63,6 +63,7 @@
     docs(project(":camera:camera-lifecycle"))
     docs(project(":camera:camera-mlkit-vision"))
     // camera-previewview is not hosted in androidx
+    docsWithoutApiSince(project(":camera:camera-testing"))
     docs(project(":camera:camera-video"))
     docs(project(":camera:camera-view"))
     docs(project(":camera:camera-viewfinder"))
diff --git a/docs/api_guidelines/dependencies.md b/docs/api_guidelines/dependencies.md
index 011fab9..5a43db3 100644
--- a/docs/api_guidelines/dependencies.md
+++ b/docs/api_guidelines/dependencies.md
@@ -107,9 +107,16 @@
 image and are made available to developers through the `<uses-library>` manifest
 tag.
 
-Examples include Wear OS extensions (`com.google.android.wearable`), Camera OEM
-extensions (`androidx.camera.extensions.impl`), and Window OEM extensions
-(`androix.window.extensions`).
+Examples include Camera OEM extensions (`androidx.camera.extensions.impl`) and
+Window OEM extensions (`androidx.window.extensions`).
+
+Extension libraries may be defined in AndroidX library projects (see
+`androidx.window.extensions`) or externally, ex. in AOSP alongside the platform.
+In either case, we recommend that libraries use extensions as pinned, rather
+than project-type, dependencies to facilitate versioning across repositories.
+
+*Do not* ship extension interfaces to Google Maven. Teams may choose to ship
+stub JARs publicly, but that is not covered by AndroidX workflows.
 
 Project dependencies on extension libraries **must** use `compileOnly`:
 
diff --git a/docs/api_guidelines/deprecation.md b/docs/api_guidelines/deprecation.md
index f5dc6d8..2d7069e 100644
--- a/docs/api_guidelines/deprecation.md
+++ b/docs/api_guidelines/deprecation.md
@@ -91,8 +91,9 @@
     artifact as `@Deprecated` and update the API files
     ([example CL](https://android-review.googlesource.com/c/platform/frameworks/support/+/1938773))
 1.  Schedule a release of the artifact as a new minor version. When you populate
-    the release notes, explain that the entire artifact has been deprecated.
-    Include the reason for deprecation and the migration strategy.
+    the release notes, explain that the entire artifact has been deprecated and
+    will no longer receive new features or bug fixes. Include the reason for
+    deprecation and the migration strategy.
 1.  After the artifact has been released, remove the artifact from the source
     tree, versions file, and tip-of-tree docs configuration
     ([example CL](https://android-review.googlesource.com/c/platform/frameworks/support/+/2061731/))
@@ -107,3 +108,58 @@
 
 After an artifact has been released as fully-deprecated, it can be removed from
 the source tree.
+
+#### Long-term support
+
+Artifacts which have been fully deprecated and removed are not required to fix
+any bugs -- including security issues -- which are reported after the library
+has been removed from source control; however, library owners *may* utilize
+release branches to provide long-term support.
+
+When working on long-term support in a release branch, you may encounter the
+following issues:
+
+-   Release metadata produced by the build system is not compatible with the
+    release scheduling tool
+-   Build targets associated with the release branch do not match targets used
+    by the snapped build ID
+-   Delta between last snapped build ID and proposed snap build ID is too large
+    and cannot be processed by the release branch management tool
+
+### Discouraging usage in Play Store
+
+[Google Play SDK Console](https://play.google.com/sdk-console/) allows library
+owners to annotate specific library versions with notes, which are shown to app
+developers in the Play Store Console, or permanently mark them as outdated,
+which shows a warning in Play Store Console asking app developers to upgrade.
+
+In both cases, library owners have the option to prevent app developers from
+releasing apps to Play Store that have been built against specific library
+versions.
+
+Generally, Jetpack discourages the use of either notes or marking versions as
+outdated. There are few cases that warrant pushing notifications to app
+developers, and it is easy to abuse notes as advertising to drive adoption. As a
+rule, upgrades to Jetpack libraries should be driven by the needs of app
+developers.
+
+Cases where notes may be used include:
+
+1.  The library is used directly, rather than transitively, and contains `P0` or
+    `P1` (ship-blocking, from the app's perspective) issues
+    -   Transitively-included libraries should instead urge their dependent
+        libraries to bump their pinned dependency versions
+1.  The library contains ship-blocking security issues. In this case, we
+    recommend preventing app releases since developers may be less aware of
+    security issues.
+1.  The library was built against a pre-release SDK which has been superseded by
+    a finalized SDK. In this case, we recommend preventing app releases since
+    the library may crash or show unexpected behavior.
+
+Cases where marking a version as outdated maybe used:
+
+1.  The library has security implications and the version is no longer receiving
+    security updates, e.g. the release branch has moved to the next version.
+
+In all cases, there must be a newer stable or bugfix release of the library that
+app developers can use to replace the outdated version.
diff --git a/docs/onboarding_images/image10.png b/docs/onboarding_images/image10.png
new file mode 100644
index 0000000..ed1fd74
--- /dev/null
+++ b/docs/onboarding_images/image10.png
Binary files differ
diff --git a/docs/onboarding_images/image6.png b/docs/onboarding_images/image6.png
new file mode 100644
index 0000000..41795ce
--- /dev/null
+++ b/docs/onboarding_images/image6.png
Binary files differ
diff --git a/docs/onboarding_images/image7.png b/docs/onboarding_images/image7.png
new file mode 100644
index 0000000..27eef7d
--- /dev/null
+++ b/docs/onboarding_images/image7.png
Binary files differ
diff --git a/docs/onboarding_images/image8.png b/docs/onboarding_images/image8.png
new file mode 100644
index 0000000..da0dc66
--- /dev/null
+++ b/docs/onboarding_images/image8.png
Binary files differ
diff --git a/docs/onboarding_images/image9.png b/docs/onboarding_images/image9.png
new file mode 100644
index 0000000..d90dcc2
--- /dev/null
+++ b/docs/onboarding_images/image9.png
Binary files differ
diff --git a/docs/testing.md b/docs/testing.md
index 8050e0a..84e47d2 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -40,6 +40,164 @@
 ([example](https://r.android.com/2428721)). You can run these tests just like
 any other JVM test using `test` Gradle task.
 
+### Adding screenshots tests using scuba library
+
+#### Prerequisites
+
+Golden project: Make sure that you have the golden directory in your root
+checkout (sibling of frameworks directory). If not re-init your repo to fetch
+the latest manifest file:
+
+```
+$ repo init -u sso://android/platform/manifest \
+    -b androidx-main && repo sync -c -j8
+```
+
+Set up your module: If your module is not using screenshot tests yet, you need
+to do the initial setup.
+
+1.  Modify your gradle file: Add dependency on the diffing library into your
+    gradle file:
+
+    ```
+    androidTestImplementation project(“:test:screenshot:screenshot”)
+    ```
+
+    Important step: Add golden asset directory to be linked to your test apk:
+
+    ```
+    android {
+        sourceSets.androidTest.assets.srcDirs +=
+            // For androidx project (not in ui dir) use "/../../golden/project"
+            project.rootDir.absolutePath + "/../../golden/compose/material/material"
+    }
+    ```
+
+    This will bundle the goldens into your apk so they can be retrieved during
+    the test.
+
+2.  Create directory and variable: In the golden directory, create a new
+    directory for your module (the directory that you added to your gradle file,
+    which in case of material was “compose/material/material”).
+
+    In your test module, create a variable pointing at your new directory:
+
+    ```
+    const val GOLDEN_MATERIAL = "compose/material/material"
+    ```
+
+#### Adding a screenshot test
+
+Here is an example of a minimal screenshot test for compose material.
+
+```
+@LargeTest
+@RunWith(JUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+class CheckboxScreenshotTest {
+    @get:Rule val composeTestRule = createComposeRule()
+    @get:Rule val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL)
+
+    @Test
+    fun checkBoxTest_checked() {
+        composeTestRule.setMaterialContent {
+            Checkbox(Modifier.wrapContentSize(Alignment.TopStart),
+                checked = true,
+                onCheckedChange = {}
+            )
+        }
+        find(isToggleable())
+            .captureToBitmap()
+            .assertAgainstGolden(screenshotRule, "checkbox_checked")
+    }
+}
+```
+
+NOTE: The string “checkbox_checked” is the unique identifier of your golden in
+your module. We use that string to name the golden file so avoid special
+characters. Please avoid any substrings like: golden, image etc. as there is no
+need - instead just describe what the image contains.
+
+#### Guidance around diffing
+
+Try to take the smallest screenshot possible. This will reduce interference from
+other elements.
+
+By default we use a MSSIM comparer. This one is based on similarity. However we
+have quite a high bar currently which is 0.98 (1 is an exact match). You can
+provide your own threshold or even opt into a pixel perfect comparer for some
+reason.
+
+Note: The bigger screenshots you take the more you sacrifice in the precision as
+you can aggregate larger diffing errors, see the examples below.
+
+![alt_text](onboarding_images/image6.png "screenshot diff at different MSSIM")
+
+#### Generating your goldens in CI (Gerrit)
+
+Upload your CL to gerrit and run presubmit. You should see your test fail.
+
+Step 1: Click on the “Test” button below:
+
+![alt_text](onboarding_images/image7.png "Presubmit link to failed test")
+
+Step 2: Click on the “Update scuba goldens” below:
+![alt_text](onboarding_images/image8.png "Update scuba button")
+
+Step 3: You should see a dashboard similar to the example below. Check-out if
+the new screenshots look as expected and if yes click approve. This will create
+a new CL.
+![alt_text](onboarding_images/image9.png "Button to approve scuba changes")
+
+Step 4: Link your original CL with the new goldens CL by setting the same Topic
+field in both CLs (any arbitrary string will do). This tells Gerrit to submit
+the CLs together, effectively providing a reference from the original CL to the
+new goldens. And re-run presubmit. Your tests should now pass!
+![alt_text](onboarding_images/image10.png "Topic for connecting cls")
+
+#### Running manually / debugging
+
+Screenshot tests can be run locally using pixel 2 api33 emulator. Start the
+emulator using [these](#emulator) steps.
+
+Wait until the emulator is running and run the tests as you would on a regular
+device.
+
+```
+$ ./gradlew <module>:cAT -Pandroid.testInstrumentationRunnerArguments.class=<class>
+```
+
+If the test passes, the results are limited to a .textproto file for each
+screenshot test. If the test fails, the results will also contain the actual
+screenshot and, if available, the golden reference image and the diff between
+the two. Note that this means that if you want to regenerate the golden image,
+you have to remove the golden image before running the test.
+
+To get the screenshot related results from the device onto your workstation, you
+can run
+
+```
+$ adb pull /sdcard/Android/data/<test-package>/cache/androidx_screenshots
+```
+
+where test-package is the identifier of you test apk, e.g.
+androidx.compose.material.test
+
+#### Locally updating the golden images
+
+After you run a screenshot test and pull the results to a desired location,
+verify that the actual images are the correct ones and copy them to the golden
+screenshots directory (the one you use to create the AndroidXScreenshotTestRule
+with) using this script.
+
+```
+androidx-main/frameworks/support/development/copy_screenshots_to_golden_repo.py \
+--input-dir=/tmp/androidx_screenshots/ --output-dir=androidx-main/golden/<test>/
+```
+
+Repeat for all screenshots, then create and upload a CL in the golden
+repository.
+
 ### What gets tested, and when {#affected-module-detector}
 
 With over 45000 tests executed on every CI run, it is necessary for us to run
@@ -81,12 +239,6 @@
 
 #### Disabling tests {#disabling-tests}
 
-To disable a device-side test in presubmit testing only -- but still have it run
-in postsubmit -- use the
-[`@FlakyTest`](https://developer.android.com/reference/androidx/test/filters/FlakyTest)
-annotation. There is currently no support for presubmit-only disabling of
-host-side tests.
-
 If you need to stop a host- or device-side test from running entirely, use
 JUnit's [`@Ignore`](http://junit.sourceforge.net/javadoc/org/junit/Ignore.html)
 annotation. Do *not* use Android's `@Suppress` annotation, which only works with
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-es/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-es/strings.xml
index feb3289..bb95933 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-es/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-es/strings.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="emoji_category_recent" msgid="7142376595414250279">"USADO RECIENTEMENTE"</string>
+    <string name="emoji_category_recent" msgid="7142376595414250279">"USADOS RECIENTEMENTE"</string>
     <string name="emoji_category_emotions" msgid="1570830970240985537">"EMOTICONOS Y EMOCIONES"</string>
     <string name="emoji_category_people" msgid="7968173366822927025">"PERSONAS"</string>
     <string name="emoji_category_animals_nature" msgid="4640771324837307541">"ANIMALES Y NATURALEZA"</string>
diff --git a/fragment/fragment/api/restricted_current.txt b/fragment/fragment/api/restricted_current.txt
index 2f50bc8..d1e02606 100644
--- a/fragment/fragment/api/restricted_current.txt
+++ b/fragment/fragment/api/restricted_current.txt
@@ -451,21 +451,28 @@
     ctor public FragmentTransitionImpl();
     method public abstract void addTarget(Object, android.view.View);
     method public abstract void addTargets(Object, java.util.ArrayList<android.view.View!>);
+    method public void animateToEnd(Object);
+    method public void animateToStart(Object);
     method public abstract void beginDelayedTransition(android.view.ViewGroup, Object?);
     method protected static void bfsAddViewChildren(java.util.List<android.view.View!>!, android.view.View!);
     method public abstract boolean canHandle(Object);
     method public abstract Object! cloneTransition(Object?);
+    method public Object? controlDelayedTransition(android.view.ViewGroup, Object);
     method protected void getBoundsOnScreen(android.view.View!, android.graphics.Rect!);
     method protected static boolean isNullOrEmpty(java.util.List!);
+    method public boolean isSeekingSupported();
+    method public boolean isSeekingSupported(Object);
     method public abstract Object! mergeTransitionsInSequence(Object?, Object?, Object?);
     method public abstract Object! mergeTransitionsTogether(Object?, Object?, Object?);
     method public abstract void removeTarget(Object, android.view.View);
     method public abstract void replaceTargets(Object, java.util.ArrayList<android.view.View!>!, java.util.ArrayList<android.view.View!>!);
     method public abstract void scheduleHideFragmentView(Object, android.view.View, java.util.ArrayList<android.view.View!>);
     method public abstract void scheduleRemoveTargets(Object, Object?, java.util.ArrayList<android.view.View!>?, Object?, java.util.ArrayList<android.view.View!>?, Object?, java.util.ArrayList<android.view.View!>?);
+    method public void setCurrentPlayTime(Object, float);
     method public abstract void setEpicenter(Object, android.graphics.Rect);
     method public abstract void setEpicenter(Object, android.view.View?);
     method public void setListenerForTransitionEnd(androidx.fragment.app.Fragment, Object, androidx.core.os.CancellationSignal, Runnable);
+    method public void setListenerForTransitionEnd(androidx.fragment.app.Fragment, Object, androidx.core.os.CancellationSignal, Runnable?, Runnable);
     method public abstract void setSharedElementTargets(Object, android.view.View, java.util.ArrayList<android.view.View!>);
     method public abstract void swapSharedElementTargets(Object?, java.util.ArrayList<android.view.View!>?, java.util.ArrayList<android.view.View!>?);
     method public abstract Object! wrapTransitionInSet(Object?);
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
index ab3b7d6..7ca4c07 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
@@ -32,6 +32,7 @@
 import androidx.core.view.ViewCompat
 import androidx.fragment.app.test.FragmentTestActivity
 import androidx.fragment.test.R
+import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.ViewModelStore
 import androidx.test.annotation.UiThreadTest
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -725,6 +726,83 @@
         assertThat(fragment1.requireView().parent).isNotNull()
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @Test
+    fun replaceOperationWithAnimatorsThenCancel() {
+        val fm1 = activityRule.activity.supportFragmentManager
+
+        val fragment1 = AnimatorFragment(R.layout.scene1)
+        fm1.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment1, "1")
+            .addToBackStack(null)
+            .commit()
+        activityRule.waitForExecution()
+
+        val fragment2 = AnimatorFragment()
+
+        fm1.beginTransaction()
+            .setCustomAnimations(
+                android.R.animator.fade_in,
+                android.R.animator.fade_out,
+                android.R.animator.fade_in,
+                android.R.animator.fade_out
+            )
+            .replace(R.id.fragmentContainer, fragment2, "2")
+            .addToBackStack(null)
+            .commit()
+        activityRule.executePendingTransactions(fm1)
+
+        assertThat(fragment2.wasStarted).isTrue()
+        // We need to wait for the exit animation to end
+        assertThat(fragment1.endLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+        val dispatcher = activityRule.activity.onBackPressedDispatcher
+        activityRule.runOnUiThread {
+            dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT))
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        fragment2.resumeLatch = CountDownLatch(1)
+
+        activityRule.runOnUiThread {
+            dispatcher.dispatchOnBackProgressed(
+                BackEventCompat(0.2F, 0.2F, 0.2F, BackEvent.EDGE_LEFT)
+            )
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        if (FragmentManager.USE_PREDICTIVE_BACK) {
+            assertThat(fragment1.startLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+            assertThat(fragment2.startLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+            assertThat(fragment1.inProgress).isTrue()
+            assertThat(fragment2.inProgress).isTrue()
+        } else {
+            assertThat(fragment1.inProgress).isFalse()
+            assertThat(fragment1.inProgress).isFalse()
+        }
+
+        activityRule.runOnUiThread {
+            dispatcher.dispatchOnBackCancelled()
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        assertThat(fragment2.wasStarted).isTrue()
+        // Now fragment1 should be animating away
+        assertThat(fragment1.isAdded).isFalse()
+
+        // Now fragment2 should be animating back in
+        assertThat(fragment2.isAdded).isTrue()
+        assertThat(fm1.findFragmentByTag("2")).isEqualTo(fragment2)
+
+        assertThat(fragment2.resumeLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+        // Make sure the original fragment was correctly readded to the container
+        assertThat(fragment2.view?.parent).isNotNull()
+
+        assertThat(fragment1.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
+        assertThat(fragment1.view).isNull()
+    }
+
     private fun assertEnterPopExit(fragment: AnimatorFragment) {
         assertFragmentAnimation(fragment, 1, true, ENTER)
 
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/SpecialEffectsControllerTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/SpecialEffectsControllerTest.kt
index a35f0c1..852fe8a 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/SpecialEffectsControllerTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/SpecialEffectsControllerTest.kt
@@ -29,6 +29,7 @@
 import androidx.testutils.withUse
 import com.google.common.truth.Truth.assertThat
 import leakcanary.DetectLeaksAfterTestSuccess
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -164,6 +165,7 @@
         }
     }
 
+    @Ignore // Ignore this test until we find a way to better test this scenario.
     @MediumTest
     @Test
     fun ensureOnlyChangeContainerStatusForCompletedOperation() {
@@ -220,17 +222,10 @@
             operation2.addCompletionListener {
                 awaitingChanges = operation2.isAwaitingContainerChanges
             }
+            val operation = controller.operationsToExecute[0]
             onActivity {
-                fragmentStateManager1.moveToExpectedState()
-                fragmentStateManager2.moveToExpectedState()
-                // However, executePendingOperations(), since we're using our
-                // TestSpecialEffectsController, does immediately call complete()
-                // which in turn calls moveToExpectedState()
-                controller.executePendingOperations()
+                operation.complete()
             }
-            // Assert that we actually moved to the STARTED state
-            assertThat(fragment1.lifecycle.currentState)
-                .isEqualTo(Lifecycle.State.STARTED)
             assertThat(awaitingChanges).isTrue()
         }
     }
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.kt b/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.kt
index 6240244..a5f9e91 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.kt
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.kt
@@ -188,13 +188,16 @@
         firstOut: Operation?,
         lastIn: Operation?
     ) {
-        // First verify that we can run all transitions together
-        val transitionImpl = transitionInfos.filterNot { transitionInfo ->
+        val filteredInfos = transitionInfos.filterNot { transitionInfo ->
             // If there is no change in visibility, we can skip the TransitionInfo
             transitionInfo.isVisibilityUnchanged
         }.filter { transitionInfo ->
             transitionInfo.handlingImpl != null
-        }.fold(null as FragmentTransitionImpl?) { chosenImpl, transitionInfo ->
+        }
+        // First verify that we can run all transitions together
+        val transitionImpl = filteredInfos.fold(
+            null as FragmentTransitionImpl?
+        ) { chosenImpl, transitionInfo ->
             val handlingImpl = transitionInfo.handlingImpl
             require(chosenImpl == null || handlingImpl === chosenImpl) {
                 "Mixing framework transitions and AndroidX transitions is not allowed. Fragment " +
@@ -215,7 +218,7 @@
         var exitingNames = ArrayList<String>()
         val firstOutViews = ArrayMap<String, View>()
         val lastInViews = ArrayMap<String, View>()
-        for (transitionInfo: TransitionInfo in transitionInfos) {
+        for (transitionInfo: TransitionInfo in filteredInfos) {
             val hasSharedElementTransition = transitionInfo.hasSharedElementTransition()
             // Compute the shared element transition between the firstOut and lastIn Fragments
             if (hasSharedElementTransition && (firstOut != null) && (lastIn != null)) {
@@ -344,12 +347,12 @@
         }
 
         val transitionEffect = TransitionEffect(
-            transitionInfos, firstOut, lastIn, transitionImpl, sharedElementTransition,
+            filteredInfos, firstOut, lastIn, transitionImpl, sharedElementTransition,
             sharedElementFirstOutViews, sharedElementLastInViews, sharedElementNameMapping,
             enteringNames, exitingNames, firstOutViews, lastInViews, isPop
         )
 
-        transitionInfos.forEach { transitionInfo ->
+        filteredInfos.forEach { transitionInfo ->
             transitionInfo.operation.addEffect(transitionEffect)
         }
     }
@@ -594,7 +597,7 @@
                     if (isHideOperation) {
                         // Specifically for hide operations with Animator, we can't
                         // applyState until the Animator finishes
-                        operation.finalState.applyState(viewToAnimate)
+                        operation.finalState.applyState(viewToAnimate, container)
                     }
                     animatorInfo.operation.completeEffect(this@AnimatorEffect)
                     if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
@@ -700,7 +703,134 @@
     ) : Effect() {
         val transitionSignal = CancellationSignal()
 
+        var controller: Any? = null
+
+        override val isSeekingSupported: Boolean
+            get() = transitionImpl.isSeekingSupported &&
+                transitionInfos.all {
+                    Build.VERSION.SDK_INT >= 34 &&
+                        it.transition != null &&
+                        transitionImpl.isSeekingSupported(it.transition)
+                }
+
+        val transitioning: Boolean
+            get() = transitionInfos.all {
+                it.operation.fragment.mTransitioning
+            }
+
+        override fun onStart(container: ViewGroup) {
+            // If the container has never been laid out, transitions will not start so
+            // so lets instantly complete them.
+            if (!ViewCompat.isLaidOut(container)) {
+                transitionInfos.forEach { transitionInfo: TransitionInfo ->
+                    val operation: Operation = transitionInfo.operation
+                    if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+                        Log.v(FragmentManager.TAG,
+                            "SpecialEffectsController: Container $container has not been " +
+                                "laid out. Skipping onStart for operation $operation")
+                    }
+                }
+                return
+            }
+            if (isSeekingSupported && transitioning) {
+                // We need to set the listener before we create the controller, but we need the
+                // controller to do the desired cancel behavior (animateToStart). So we use this
+                // lambda to set the proper cancel behavior to pass into the listener before the
+                // function is created.
+                var seekCancelLambda: (() -> Unit)? = null
+                // Now set up our completion signal on the completely merged transition set
+                val (enteringViews, mergedTransition) =
+                    createMergedTransition(container, lastIn, firstOut)
+                transitionInfos.map { it.operation }.forEach { operation ->
+                    val cancelRunnable = Runnable { seekCancelLambda?.invoke() }
+                    transitionImpl.setListenerForTransitionEnd(
+                        operation.fragment,
+                        mergedTransition,
+                        transitionSignal,
+                        cancelRunnable,
+                        Runnable {
+                            if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+                                Log.v(FragmentManager.TAG,
+                                    "Transition for operation $operation has completed")
+                            }
+                            operation.completeEffect(this)
+                        })
+                }
+
+                runTransition(enteringViews, container) {
+                    controller =
+                        transitionImpl.controlDelayedTransition(container, mergedTransition)
+                    // If we fail to create a controller, it must be because of the container or
+                    // the transition so we should throw an error.
+                    check(controller != null) {
+                        "Unable to start transition $mergedTransition for container $container."
+                    }
+                    seekCancelLambda = { transitionImpl.animateToStart(controller!!) }
+                    if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+                        Log.v(FragmentManager.TAG,
+                            "Started executing operations from $firstOut to $lastIn")
+                    }
+                }
+            }
+        }
+
+        override fun onProgress(backEvent: BackEventCompat, container: ViewGroup) {
+            controller?.let { transitionImpl.setCurrentPlayTime(it, backEvent.progress) }
+        }
+
         override fun onCommit(container: ViewGroup) {
+            // If the container has never been laid out, transitions will not start so
+            // so lets instantly complete them.
+            if (!ViewCompat.isLaidOut(container)) {
+                transitionInfos.forEach { transitionInfo: TransitionInfo ->
+                    val operation: Operation = transitionInfo.operation
+                    if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+                        Log.v(FragmentManager.TAG,
+                            "SpecialEffectsController: Container $container has not been " +
+                                "laid out. Completing operation $operation")
+                    }
+                    transitionInfo.operation.completeEffect(this)
+                }
+                return
+            }
+            if (controller != null) {
+                transitionImpl.animateToEnd(controller!!)
+                if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+                    Log.v(FragmentManager.TAG,
+                        "Ending execution of operations from $firstOut to $lastIn")
+                }
+            } else {
+                val (enteringViews, mergedTransition) =
+                    createMergedTransition(container, lastIn, firstOut)
+                // Now set up our completion signal on the completely merged transition set
+                transitionInfos.map { it.operation }.forEach { operation ->
+                    transitionImpl.setListenerForTransitionEnd(
+                        operation.fragment,
+                        mergedTransition,
+                        transitionSignal,
+                        Runnable {
+                            if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+                                Log.v(FragmentManager.TAG,
+                                    "Transition for operation $operation has completed")
+                            }
+                            operation.completeEffect(this)
+                        })
+                }
+                runTransition(enteringViews, container) {
+                    transitionImpl.beginDelayedTransition(container, mergedTransition)
+                }
+                if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+                    Log.v(FragmentManager.TAG,
+                        "Completed executing operations from $firstOut to $lastIn")
+                }
+            }
+        }
+
+        private fun createMergedTransition(
+            container: ViewGroup,
+            lastIn: Operation?,
+            firstOut: Operation?
+        ): Pair<ArrayList<View>, Any> {
             // Every transition needs to target at least one View so that they
             // don't interfere with one another. This is the view we use
             // in cases where there are no real views to target
@@ -772,26 +902,13 @@
             // Now iterate through the set of transitions and merge them together
             for (transitionInfo: TransitionInfo in transitionInfos) {
                 val operation: Operation = transitionInfo.operation
-                if (transitionInfo.isVisibilityUnchanged) {
-                    // No change in visibility, so we can immediately complete the transition
-                    transitionInfo.operation.completeEffect(this)
-                    continue
-                }
                 val transition = transitionImpl.cloneTransition(transitionInfo.transition)
-                val involvedInSharedElementTransition = (sharedElementTransition != null &&
-                    (operation === firstOut || operation === lastIn))
-                if (transition == null) {
-                    // Nothing more to do if the transition is null
-                    if (!involvedInSharedElementTransition) {
-                        // Only complete the transition if this fragment isn't involved
-                        // in the shared element transition (as otherwise we need to wait
-                        // for that to finish)
-                        transitionInfo.operation.completeEffect(this)
-                    }
-                } else {
+                if (transition != null) {
                     // Target the Transition to *only* the set of transitioning views
                     val transitioningViews = ArrayList<View>()
                     captureTransitioningViews(transitioningViews, operation.fragment.mView)
+                    val involvedInSharedElementTransition = (sharedElementTransition != null &&
+                        (operation === firstOut || operation === lastIn))
                     if (involvedInSharedElementTransition) {
                         // Remove all of the shared element views from the transition
                         if (operation === firstOut) {
@@ -804,8 +921,10 @@
                         transitionImpl.addTarget(transition, nonExistentView)
                     } else {
                         transitionImpl.addTargets(transition, transitioningViews)
-                        transitionImpl.scheduleRemoveTargets(transition, transition,
-                            transitioningViews, null, null, null, null)
+                        transitionImpl.scheduleRemoveTargets(
+                            transition, transition,
+                            transitioningViews, null, null, null, null
+                        )
                         if (operation.finalState === Operation.State.GONE) {
                             // We're hiding the Fragment. This requires a bit of extra work
                             // First, we need to avoid immediately applying the container change as
@@ -815,8 +934,10 @@
                             // essentially doing what applyState() would do for us
                             val transitioningViewsToHide = ArrayList(transitioningViews)
                             transitioningViewsToHide.remove(operation.fragment.mView)
-                            transitionImpl.scheduleHideFragmentView(transition,
-                                operation.fragment.mView, transitioningViewsToHide)
+                            transitionImpl.scheduleHideFragmentView(
+                                transition,
+                                operation.fragment.mView, transitioningViewsToHide
+                            )
                             // This OneShotPreDrawListener gets fired before the delayed start of
                             // the Transition and changes the visibility of any exiting child views
                             // that *ARE NOT* shared element transitions. The TransitionManager then
@@ -840,11 +961,13 @@
                     if (transitionInfo.isOverlapAllowed) {
                         // Overlap is allowed, so add them to the mergeTransition set
                         mergedTransition = transitionImpl.mergeTransitionsTogether(
-                            mergedTransition, transition, null)
+                            mergedTransition, transition, null
+                        )
                     } else {
                         // Overlap is not allowed, add them to the mergedNonOverlappingTransition
                         mergedNonOverlappingTransition = transitionImpl.mergeTransitionsTogether(
-                            mergedNonOverlappingTransition, transition, null)
+                            mergedNonOverlappingTransition, transition, null
+                        )
                     }
                 }
             }
@@ -854,51 +977,14 @@
             mergedTransition = transitionImpl.mergeTransitionsInSequence(mergedTransition,
                 mergedNonOverlappingTransition, sharedElementTransition)
 
-            // If there's no transitions playing together, no non-overlapping transitions,
-            // and no shared element transitions, mergedTransition will be null and
-            // there's nothing else we need to do
-            if (mergedTransition == null) {
-                return
-            }
-            // Now set up our completion signal on the completely merged transition set
-            transitionInfos.filterNot { transitionInfo ->
-                // If there's change in visibility, we've already completed the transition
-                transitionInfo.isVisibilityUnchanged
-            }.forEach { transitionInfo: TransitionInfo ->
-                val transition: Any? = transitionInfo.transition
-                val operation: Operation = transitionInfo.operation
-                val involvedInSharedElementTransition = sharedElementTransition != null &&
-                    (operation === firstOut || operation === lastIn)
-                if (transition != null || involvedInSharedElementTransition) {
-                    // If the container has never been laid out, transitions will not start so
-                    // so lets instantly complete them.
-                    if (!ViewCompat.isLaidOut(container)) {
-                        if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
-                            Log.v(FragmentManager.TAG,
-                                "SpecialEffectsController: Container $container has not been " +
-                                    "laid out. Completing operation $operation")
-                        }
-                        transitionInfo.operation.completeEffect(this)
-                    } else {
-                        transitionImpl.setListenerForTransitionEnd(
-                            transitionInfo.operation.fragment,
-                            mergedTransition,
-                            transitionSignal,
-                            Runnable {
-                                transitionInfo.operation.completeEffect(this)
-                                if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
-                                    Log.v(FragmentManager.TAG,
-                                        "Transition for operation $operation has completed")
-                                }
-                            })
-                    }
-                }
-            }
-            // Transitions won't run if the container isn't laid out so
-            // we can return early here to avoid doing unnecessary work.
-            if (!ViewCompat.isLaidOut(container)) {
-                return
-            }
+            return Pair(enteringViews, mergedTransition)
+        }
+
+        private fun runTransition(
+            enteringViews: ArrayList<View>,
+            container: ViewGroup,
+            executeTransition: (() -> Unit)
+        ) {
             // First, hide all of the entering views so they're in
             // the correct initial state
             setViewVisibility(enteringViews, View.INVISIBLE)
@@ -917,7 +1003,7 @@
                 }
             }
             // Now actually start the transition
-            transitionImpl.beginDelayedTransition(container, mergedTransition)
+            executeTransition.invoke()
             transitionImpl.setNameOverridesReordered(container, sharedElementFirstOutViews,
                 sharedElementLastInViews, inNames, sharedElementNameMapping)
             // Then, show all of the entering views, putting them into
@@ -925,11 +1011,6 @@
             setViewVisibility(enteringViews, View.VISIBLE)
             transitionImpl.swapSharedElementTargets(sharedElementTransition,
                 sharedElementFirstOutViews, sharedElementLastInViews)
-
-            if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
-                Log.v(FragmentManager.TAG,
-                    "Completed executing operations from $firstOut to $lastIn")
-            }
         }
 
         override fun onCancel(container: ViewGroup) {
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
index 0718eba..3a1220f 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -2922,7 +2922,9 @@
                 mHost.getHandler().post(new Runnable() {
                     @Override
                     public void run() {
-                        controller.executePendingOperations();
+                        if (controller.isPendingExecute()) {
+                            controller.executePendingOperations();
+                        }
                     }
                 });
             } else {
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionCompat21.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionCompat21.java
index 0f0145d..8c7d538 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionCompat21.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionCompat21.java
@@ -21,6 +21,7 @@
 import android.transition.Transition;
 import android.transition.TransitionManager;
 import android.transition.TransitionSet;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -221,6 +222,27 @@
     }
 
     @Override
+    public boolean isSeekingSupported() {
+        if (FragmentManager.isLoggingEnabled(Log.INFO)) {
+            Log.i(FragmentManager.TAG,
+                    "Predictive back not available using Framework Transitions. Please switch"
+                            + " to AndroidX Transition 1.5.0 or higher to enable seeking.");
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isSeekingSupported(@NonNull Object transition) {
+        if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+            Log.v(FragmentManager.TAG,
+                    "Predictive back not available for framework transition "
+                            + transition + ". Please switch to AndroidX Transition 1.5.0 or higher "
+                            + "to enable seeking.");
+        }
+        return false;
+    }
+
+    @Override
     public void scheduleRemoveTargets(@NonNull final Object overallTransitionObj,
             @Nullable final Object enterTransition, @Nullable final ArrayList<View> enteringViews,
             @Nullable final Object exitTransition, @Nullable final ArrayList<View> exitingViews,
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionImpl.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionImpl.java
index e9a7514..6a8d444 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionImpl.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionImpl.java
@@ -21,6 +21,7 @@
 import android.annotation.SuppressLint;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
@@ -150,6 +151,49 @@
             @Nullable Object transition);
 
     /**
+     * Returns {@code true} if the Transition is seekable.
+     */
+    public boolean isSeekingSupported() {
+        if (FragmentManager.isLoggingEnabled(Log.INFO)) {
+            Log.i(FragmentManager.TAG,
+                    "Older versions of AndroidX Transition do not support seeking. Add dependency "
+                            + "on AndroidX Transition 1.5.0 or higher to enable seeking.");
+        }
+        return false;
+    }
+
+    /**
+     * Returns {@code true} if the Transition is seekable.
+     */
+    public boolean isSeekingSupported(@NonNull Object transition) {
+        return false;
+    }
+
+    /**
+     * Allows for controlling a seekable transition
+     */
+    @Nullable
+    public Object controlDelayedTransition(@NonNull ViewGroup sceneRoot,
+            @NonNull Object transition) {
+        return null;
+    }
+
+    /**
+     * Uses given progress to set the current play time of the transition.
+     */
+    public void setCurrentPlayTime(@NonNull Object transitionController, float progress) { }
+
+    /**
+     * Animate the transition to end.
+     */
+    public void animateToEnd(@NonNull Object transitionController) { }
+
+    /**
+     * Animate the transition to start.
+     */
+    public void animateToStart(@NonNull Object transitionController) { }
+
+    /**
      * Prepares for setting the shared element names by gathering the names of the incoming
      * shared elements and clearing them. {@link #setNameOverridesReordered(View, ArrayList,
      * ArrayList, ArrayList, Map)} must be called after this to complete setting the shared element
@@ -230,6 +274,32 @@
     public void setListenerForTransitionEnd(@NonNull final Fragment outFragment,
             @NonNull Object transition, @NonNull CancellationSignal signal,
             @NonNull Runnable transitionCompleteRunnable) {
+        setListenerForTransitionEnd(
+                outFragment, transition, signal, null, transitionCompleteRunnable
+        );
+    }
+
+    /**
+     * Set a listener for Transition end events. The default behavior immediately completes the
+     * transition.
+     *
+     * Use this when the given transition is seeking. The cancelRunnable should handle
+     * cleaning up the transition when seeking is cancelled.
+     *
+     * If the transition is not seeking, you should use
+     * {@link #setListenerForTransitionEnd(Fragment, Object, CancellationSignal, Runnable)}.
+     *
+     * @param outFragment The first fragment that is exiting
+     * @param transition all transitions to be executed on a single container
+     * @param signal used indicate the desired behavior on transition cancellation
+     * @param cancelRunnable runnable to handle the logic when the signal is cancelled
+     * @param transitionCompleteRunnable used to notify the FragmentManager when a transition is
+     *                                   complete
+     */
+    public void setListenerForTransitionEnd(@NonNull final Fragment outFragment,
+            @NonNull Object transition, @NonNull CancellationSignal signal,
+            @Nullable Runnable cancelRunnable,
+            @NonNull Runnable transitionCompleteRunnable) {
         transitionCompleteRunnable.run();
     }
 
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.kt b/fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.kt
index 8a67e9c..0e3ebf0 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.kt
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/SpecialEffectsController.kt
@@ -149,7 +149,7 @@
             // Ensure that we still run the applyState() call for pending operations
             operation.addCompletionListener {
                 if (pendingOperations.contains(operation)) {
-                    operation.finalState.applyState(operation.fragment.mView)
+                    operation.finalState.applyState(operation.fragment.mView, container)
                 }
             }
             // Ensure that we remove the Operation from the list of
@@ -180,6 +180,10 @@
         }
     }
 
+    fun isPendingExecute(): Boolean {
+        return pendingOperations.isNotEmpty()
+    }
+
     fun forcePostponedExecutePendingOperations() {
         if (isContainerPostponed) {
             if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
@@ -206,7 +210,29 @@
             return
         }
         synchronized(pendingOperations) {
-            if (pendingOperations.isNotEmpty()) {
+            if (pendingOperations.isEmpty()) {
+                val currentlyRunningOperations = runningOperations.toMutableList()
+                runningOperations.clear()
+                for (operation in currentlyRunningOperations) {
+                    if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+                        Log.v(
+                            FragmentManager.TAG,
+                            "SpecialEffectsController: Cancelling operation $operation " +
+                                "with no incoming pendingOperations"
+                        )
+                    }
+                    // Cancel the currently running operation immediately as this is the case
+                    // where we got an handleOnBackCanceled callback and we don't want to run
+                    // any effects back to start cause they will have already been seeked to
+                    // start
+                    operation.cancel(container, false)
+                    if (!operation.isComplete) {
+                        // Re-add any animations that didn't synchronously call complete()
+                        // to continue to track them as running operations
+                        runningOperations.add(operation)
+                    }
+                }
+            } else {
                 val currentlyRunningOperations = runningOperations.toMutableList()
                 runningOperations.clear()
                 for (operation in currentlyRunningOperations) {
@@ -216,7 +242,9 @@
                             "SpecialEffectsController: Cancelling operation $operation"
                         )
                     }
-                    // Cancel with seeking if the fragment is transitioning
+                    // Cancel with seeking if the fragment is transitioning as this is the case
+                    // where another operation is about to run while we are still seeking
+                    // so we should move our current effect back to the start.
                     operation.cancel(container, operation.fragment.mTransitioning)
                     if (!operation.isComplete) {
                         // Re-add any animations that didn't synchronously call complete()
@@ -275,7 +303,7 @@
 
     internal fun applyContainerChangesToOperation(operation: Operation) {
         if (operation.isAwaitingContainerChanges) {
-            operation.finalState.applyState(operation.fragment.requireView())
+            operation.finalState.applyState(operation.fragment.requireView(), container)
             operation.isAwaitingContainerChanges = false
         }
     }
@@ -473,8 +501,9 @@
              * Applies this state to the given View.
              *
              * @param view The View to apply this state to.
+             * @param container The ViewGroup to add the view too if it does not have a parent.
              */
-            fun applyState(view: View) {
+            fun applyState(view: View, container: ViewGroup) {
                 when (this) {
                     REMOVED -> {
                         val parent = view.parent as? ViewGroup
@@ -496,6 +525,16 @@
                                     "Setting view $view to VISIBLE"
                             )
                         }
+                        val parent = view.parent as? ViewGroup
+                        if (parent == null) {
+                            if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+                                Log.v(
+                                    FragmentManager.TAG, "SpecialEffectsController: " +
+                                        "Adding view $view to Container $container"
+                                )
+                            }
+                            container.addView(view)
+                        }
                         view.visibility = View.VISIBLE
                     }
 
@@ -633,6 +672,7 @@
                     // moves it back to ADDING
                     this.finalState = State.VISIBLE
                     this.lifecycleImpact = LifecycleImpact.ADDING
+                    this.isAwaitingContainerChanges = true
                 }
                 LifecycleImpact.REMOVING -> {
                     if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
@@ -646,6 +686,7 @@
                     // Any REMOVING operation overrides whatever we had before
                     this.finalState = State.REMOVED
                     this.lifecycleImpact = LifecycleImpact.REMOVING
+                    this.isAwaitingContainerChanges = true
                 }
                 LifecycleImpact.NONE -> // This is a hide or show operation
                     if (this.finalState != State.REMOVED) {
diff --git a/glance/OWNERS b/glance/OWNERS
index 8589cda..233edb7 100644
--- a/glance/OWNERS
+++ b/glance/OWNERS
@@ -1,9 +1,6 @@
 # Bug component: 1096766
-andreykulikov@google.com
-jgarside@google.com
-pbdr@google.com
-zoepage@google.com
+bbade@google.com
+justinkoh@google.com
+shamalip@google.com
 wvk@google.com
-truongvi@google.com
 zakcohen@google.com
-msab@google.com
diff --git a/glance/glance-appwidget-preview/lint-baseline.xml b/glance/glance-appwidget-preview/lint-baseline.xml
new file mode 100644
index 0000000..88ba6f5
--- /dev/null
+++ b/glance/glance-appwidget-preview/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.2.0-alpha15" type="baseline" client="cli" dependencies="false" name="AGP (8.2.0-alpha15)" variant="all" version="8.2.0-alpha15">
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="                .all { it }"
+        errorLine2="                 ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/preview/ComposableInvoker.kt"/>
+    </issue>
+
+</issues>
diff --git a/glance/glance-appwidget-preview/src/main/java/androidx/glance/appwidget/preview/ComposableInvoker.kt b/glance/glance-appwidget-preview/src/main/java/androidx/glance/appwidget/preview/ComposableInvoker.kt
index 2e1cba7..799ee8e 100644
--- a/glance/glance-appwidget-preview/src/main/java/androidx/glance/appwidget/preview/ComposableInvoker.kt
+++ b/glance/glance-appwidget-preview/src/main/java/androidx/glance/appwidget/preview/ComposableInvoker.kt
@@ -33,7 +33,6 @@
      * Returns true if the [methodTypes] and [actualTypes] are compatible. This means that every
      * `actualTypes[n]` are assignable to `methodTypes[n]`.
      */
-    @Suppress("ListIterator")
     private fun compatibleTypes(
         methodTypes: Array<Class<*>>,
         actualTypes: Array<Class<*>>
diff --git a/glance/glance-appwidget-testing/api/current.txt b/glance/glance-appwidget-testing/api/current.txt
index 5886053..7dfb9e9 100644
--- a/glance/glance-appwidget-testing/api/current.txt
+++ b/glance/glance-appwidget-testing/api/current.txt
@@ -42,6 +42,9 @@
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartServiceAction(android.content.Intent intent, optional boolean isForegroundService);
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartServiceAction(Class<? extends android.app.Service> serviceClass, optional boolean isForegroundService);
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isChecked();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isIndeterminateCircularProgressIndicator();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isIndeterminateLinearProgressIndicator();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isLinearProgressIndicator(float progress);
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isNotChecked();
   }
 
diff --git a/glance/glance-appwidget-testing/api/restricted_current.txt b/glance/glance-appwidget-testing/api/restricted_current.txt
index 5886053..7dfb9e9 100644
--- a/glance/glance-appwidget-testing/api/restricted_current.txt
+++ b/glance/glance-appwidget-testing/api/restricted_current.txt
@@ -42,6 +42,9 @@
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartServiceAction(android.content.Intent intent, optional boolean isForegroundService);
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartServiceAction(Class<? extends android.app.Service> serviceClass, optional boolean isForegroundService);
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isChecked();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isIndeterminateCircularProgressIndicator();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isIndeterminateLinearProgressIndicator();
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isLinearProgressIndicator(float progress);
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> isNotChecked();
   }
 
diff --git a/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/GlanceAppWidgetUnitTestEnvironment.kt b/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/GlanceAppWidgetUnitTestEnvironment.kt
index 674c8c5..6826664 100644
--- a/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/GlanceAppWidgetUnitTestEnvironment.kt
+++ b/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/GlanceAppWidgetUnitTestEnvironment.kt
@@ -34,8 +34,10 @@
 import androidx.glance.appwidget.RemoteViewsRoot
 import androidx.glance.session.globalSnapshotMonitor
 import androidx.glance.testing.GlanceNodeAssertion
+import androidx.glance.testing.GlanceNodeAssertionCollection
 import androidx.glance.testing.GlanceNodeMatcher
 import androidx.glance.testing.TestContext
+import androidx.glance.testing.matcherToSelector
 import androidx.glance.testing.unit.GlanceMappedNode
 import androidx.glance.testing.unit.MappedNode
 import kotlin.time.Duration
@@ -154,10 +156,17 @@
     ): GlanceNodeAssertion<MappedNode, GlanceMappedNode> {
         // Always let all the enqueued tasks finish before inspecting the tree.
         testScope.testScheduler.runCurrent()
-        // Calling onNode resets the previously matched nodes and starts a new matching chain.
-        testContext.reset()
         // Delegates matching to the next assertion.
-        return GlanceNodeAssertion(matcher, testContext)
+        return GlanceNodeAssertion(testContext, matcher.matcherToSelector())
+    }
+
+    override fun onAllNodes(
+        matcher: GlanceNodeMatcher<MappedNode>
+    ): GlanceNodeAssertionCollection<MappedNode, GlanceMappedNode> {
+        // Always let all the enqueued tasks finish before inspecting the tree.
+        testScope.testScheduler.runCurrent()
+        // Delegates matching to the next assertion.
+        return GlanceNodeAssertionCollection(testContext, matcher.matcherToSelector())
     }
 
     override fun setAppWidgetSize(size: DpSize) {
diff --git a/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/UnitTestAssertionExtensions.kt b/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/UnitTestAssertionExtensions.kt
index 7bf1b3b..1cddd3a 100644
--- a/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/UnitTestAssertionExtensions.kt
+++ b/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/UnitTestAssertionExtensions.kt
@@ -28,7 +28,8 @@
 import androidx.glance.testing.unit.MappedNode
 
 // This file contains (appWidget-specific) convenience assertion shorthands for unit tests that
-// delegate calls to "assert(matcher)". For assertions common to surfaces, see AssertionExtension
+// delegate calls to "assert(matcher)". For assertions common to surfaces, see equivalent file in
+// base layer testing library.
 
 internal typealias UnitTestAssertion = GlanceNodeAssertion<MappedNode, GlanceMappedNode>
 
@@ -115,7 +116,7 @@
  * Asserts that a given node has a clickable set with action that sends a broadcast.
  *
  * @param receiverClass class of the broadcast receiver that is expected to have been passed in the
- *                      actionSendBroadcast` method call.
+ *                      `actionSendBroadcast` method call.
  * @throws AssertionError if the matcher does not match or the node can no longer be found.
  */
 fun UnitTestAssertion.assertHasSendBroadcastClickAction(
@@ -128,7 +129,7 @@
  * @param intentAction the intent action of the broadcast receiver that is expected to  have been
  *                     passed in the `actionSendBroadcast` method call.
  * @param componentName optional [ComponentName] of the target broadcast receiver that is expected
- *                      to have been passed in the actionSendBroadcast` method call.
+ *                      to have been passed in the `actionSendBroadcast` method call.
  * @throws AssertionError if the matcher does not match or the node can no longer be found.
  */
 fun UnitTestAssertion.assertHasSendBroadcastClickAction(
@@ -140,7 +141,7 @@
  * Asserts that a given node has a clickable set with action that sends a broadcast.
  *
  * @param componentName [ComponentName] of the target broadcast receiver that is expected to have
- *                      been passed in the actionSendBroadcast` method call.
+ *                      been passed in the `actionSendBroadcast` method call.
  * @throws AssertionError if the matcher does not match or the node can no longer be found.
  */
 fun UnitTestAssertion.assertHasSendBroadcastClickAction(
diff --git a/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/UnitTestFilters.kt b/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/UnitTestFilters.kt
index 0f3f231..95f5846 100644
--- a/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/UnitTestFilters.kt
+++ b/glance/glance-appwidget-testing/src/main/java/androidx/glance/appwidget/testing/unit/UnitTestFilters.kt
@@ -25,6 +25,8 @@
 import androidx.glance.action.ActionModifier
 import androidx.glance.action.ActionParameters
 import androidx.glance.action.actionParametersOf
+import androidx.glance.appwidget.EmittableCircularProgressIndicator
+import androidx.glance.appwidget.EmittableLinearProgressIndicator
 import androidx.glance.appwidget.action.SendBroadcastActionAction
 import androidx.glance.appwidget.action.SendBroadcastClassAction
 import androidx.glance.appwidget.action.SendBroadcastComponentAction
@@ -33,6 +35,7 @@
 import androidx.glance.appwidget.action.StartServiceClassAction
 import androidx.glance.appwidget.action.StartServiceComponentAction
 import androidx.glance.appwidget.action.StartServiceIntentAction
+import androidx.glance.testing.GlanceNodeAssertionsProvider
 import androidx.glance.testing.GlanceNodeMatcher
 import androidx.glance.testing.unit.MappedNode
 
@@ -40,7 +43,8 @@
  * Returns a matcher that matches if a node is checkable (e.g. radio button, switch, checkbox)
  * and is checked.
  *
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
  * matching node(s) or in assertions to validate that node(s) satisfy the condition.
  */
 fun isChecked(): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
@@ -54,7 +58,8 @@
  * Returns a matcher that matches if a node is checkable (e.g. radio button, switch, checkbox)
  * but is not checked.
  *
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
  * matching node(s) or in assertions to validate that node(s) satisfy the condition.
  */
 fun isNotChecked(): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
@@ -67,7 +72,8 @@
 /**
  * Returns a matcher that matches if a node has a clickable set with action that starts an activity.
  *
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
  * matching node(s) or in assertions to validate that node(s) satisfy the condition.
  *
  * @param intent the intent for launching an activity that is expected to have been passed in the
@@ -110,7 +116,8 @@
 /**
  * Returns a matcher that matches if a node has a clickable set with action that starts a service.
  *
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
  * matching node(s) or in assertions to validate that node(s) satisfy the condition.
  *
  * @param serviceClass class of the service to launch that is expected to have been passed in the
@@ -143,7 +150,8 @@
 /**
  * Returns a matcher that matches if a node has a clickable set with action that starts a service.
  *
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
  * matching node(s) or in assertions to validate that node(s) satisfy the condition.
  *
  * @param componentName component of the service to launch that is expected to have been passed in
@@ -176,7 +184,8 @@
 /**
  * Returns a matcher that matches if a node has a clickable set with action that starts a service.
  *
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
  * matching node(s) or in assertions to validate that node(s) satisfy the condition.
  *
  * @param intent the intent for launching the service that is expected to have been passed in
@@ -209,7 +218,8 @@
 /**
  * Returns a matcher that matches if a node has a clickable set with action that sends a broadcast.
  *
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
  * matching node(s) or in assertions to validate that node(s) satisfy the condition.
  *
  * @param receiverClass class of the broadcast receiver that is expected to have been passed in the
@@ -234,7 +244,8 @@
 /**
  * Returns a matcher that matches if a node has a clickable set with action that sends a broadcast.
  *
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
  * matching node(s) or in assertions to validate that node(s) satisfy the condition.
  *
  * @param intentAction the intent action of the broadcast receiver that is expected to  have been
@@ -271,7 +282,8 @@
 /**
  * Returns a matcher that matches if a node has a clickable set with action that sends a broadcast.
  *
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
  * matching node(s) or in assertions to validate that node(s) satisfy the condition.
  *
  * @param componentName [ComponentName] of the target broadcast receiver that is expected to have
@@ -296,7 +308,8 @@
 /**
  * Returns a matcher that matches if a node has a clickable set with action that sends a broadcast.
  *
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
  * matching node(s) or in assertions to validate that node(s) satisfy the condition.
  *
  * @param intent the intent for sending broadcast  that is expected to  have been passed in the
@@ -317,3 +330,52 @@
         false
     }
 }
+
+/**
+ * Returns a matcher that matches if a given node is a linear progress indicator with given progress
+ * value.
+ *
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
+ * matching node(s) or in assertions to validate that node(s) satisfy the condition.
+ *
+ * @param progress the expected value of the current progress
+ */
+fun isLinearProgressIndicator(
+    /*@FloatRange(from = 0.0, to = 1.0)*/
+    progress: Float
+): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
+    description = "is a linear progress indicator with progress value: $progress"
+) { node ->
+    val emittable = node.value.emittable
+    emittable is EmittableLinearProgressIndicator &&
+        !emittable.indeterminate &&
+        emittable.progress == progress
+}
+
+/**
+ * Returns a matcher that matches if a given node is an indeterminate progress bar.
+ *
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
+ * matching node(s) or in assertions to validate that node(s) satisfy the condition.
+ */
+fun isIndeterminateLinearProgressIndicator(): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
+    description = "is an indeterminate linear progress indicator"
+) { node ->
+    val emittable = node.value.emittable
+    emittable is EmittableLinearProgressIndicator && emittable.indeterminate
+}
+
+/**
+ * Returns a matcher that matches if a given node is an indeterminate circular progress indicator.
+ *
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
+ * matching node(s) or in assertions to validate that node(s) satisfy the condition.
+ */
+fun isIndeterminateCircularProgressIndicator(): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
+    description = "is an indeterminate circular progress indicator"
+) { node ->
+    node.value.emittable is EmittableCircularProgressIndicator
+}
diff --git a/glance/glance-appwidget-testing/src/test/kotlin/androidx/glance/appwidget/testing/unit/GlanceAppWidgetUnitTestEnvironmentTest.kt b/glance/glance-appwidget-testing/src/test/kotlin/androidx/glance/appwidget/testing/unit/GlanceAppWidgetUnitTestEnvironmentTest.kt
index a1449d8..3c4e2d9 100644
--- a/glance/glance-appwidget-testing/src/test/kotlin/androidx/glance/appwidget/testing/unit/GlanceAppWidgetUnitTestEnvironmentTest.kt
+++ b/glance/glance-appwidget-testing/src/test/kotlin/androidx/glance/appwidget/testing/unit/GlanceAppWidgetUnitTestEnvironmentTest.kt
@@ -32,9 +32,13 @@
 import androidx.glance.ImageProvider
 import androidx.glance.LocalSize
 import androidx.glance.appwidget.ImageProvider
+import androidx.glance.appwidget.lazy.GridCells
+import androidx.glance.appwidget.lazy.LazyColumn
+import androidx.glance.appwidget.lazy.LazyVerticalGrid
 import androidx.glance.appwidget.testing.test.R
 import androidx.glance.currentState
 import androidx.glance.layout.Column
+import androidx.glance.layout.Row
 import androidx.glance.layout.Spacer
 import androidx.glance.semantics.semantics
 import androidx.glance.semantics.testTag
@@ -164,6 +168,58 @@
 
         onNode(hasTestTag("mutable-test")).assert(hasText("initial"))
     }
+
+    @Test
+    fun runTest_onMultipleNodesMatchedAcrossHierarchy() = runGlanceAppWidgetUnitTest {
+        provideComposable {
+            Column {
+                Row {
+                    Text("text-row")
+                }
+                Spacer()
+                Text("text-in-column")
+            }
+        }
+
+        onAllNodes(hasText(text = "text-")).assertCountEquals(2)
+    }
+
+    @Test
+    fun runTest_lazyColumnChildren() = runGlanceAppWidgetUnitTest {
+        provideComposable {
+            LazyColumn(modifier = GlanceModifier.semantics { testTag = "test-list" }) {
+                item { Text("text-1") }
+                item { Text("text-2") }
+            }
+        }
+
+        onNode(hasTestTag("test-list"))
+            .onChildren()
+            .assertCountEquals(2)
+            .filter(hasText("text-1"))
+            .assertCountEquals(1)
+    }
+
+    @Test
+    fun runTest_lazyGridChildren() = runGlanceAppWidgetUnitTest {
+        provideComposable {
+            LazyVerticalGrid(
+                modifier = GlanceModifier.semantics { testTag = "test-list" },
+                gridCells = GridCells.Fixed(2)
+            ) {
+                item { Text("text-1") }
+                item { Text("text-2") }
+                item { Text("text-3") }
+                item { Text("text-4") }
+            }
+        }
+
+        onNode(hasTestTag("test-list"))
+            .onChildren()
+            .assertCountEquals(4)
+            .filter(hasText("text-1"))
+            .assertCountEquals(1)
+    }
 }
 
 private val toggleKey = booleanPreferencesKey("title_toggled_key")
diff --git a/glance/glance-appwidget-testing/src/test/kotlin/androidx/glance/appwidget/testing/unit/UnitTestFiltersTest.kt b/glance/glance-appwidget-testing/src/test/kotlin/androidx/glance/appwidget/testing/unit/UnitTestFiltersTest.kt
new file mode 100644
index 0000000..b444af1
--- /dev/null
+++ b/glance/glance-appwidget-testing/src/test/kotlin/androidx/glance/appwidget/testing/unit/UnitTestFiltersTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.glance.appwidget.testing.unit
+
+import androidx.glance.appwidget.EmittableCircularProgressIndicator
+import androidx.glance.appwidget.EmittableLinearProgressIndicator
+import androidx.glance.layout.EmittableColumn
+import androidx.glance.testing.unit.getGlanceNodeAssertionFor
+import com.google.common.truth.ExpectFailure.assertThat
+import org.junit.Assert
+import org.junit.Test
+
+class UnitTestFiltersTest {
+    @Test
+    fun isCircularProgressIndicator_match() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children += EmittableCircularProgressIndicator()
+            },
+            onNodeMatcher = isIndeterminateCircularProgressIndicator()
+        )
+
+        nodeAssertion.assertExists()
+        // no error
+    }
+
+    @Test
+    fun isCircularProgressIndicator_noMatch_assertionError() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children += EmittableLinearProgressIndicator()
+            },
+            onNodeMatcher = isIndeterminateCircularProgressIndicator()
+        )
+
+        val assertionError = Assert.assertThrows(AssertionError::class.java) {
+            nodeAssertion.assertExists()
+        }
+
+        assertThat(assertionError)
+            .hasMessageThat()
+            .contains(
+                "Failed assertExists" +
+                    "\nReason: Expected '1' node(s) matching condition: " +
+                    "is an indeterminate circular progress indicator, but found '0'"
+            )
+    }
+
+    @Test
+    fun isIndeterminateLinearProgressIndicator_match() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children += EmittableLinearProgressIndicator().apply {
+                    indeterminate = true
+                }
+            },
+            onNodeMatcher = isIndeterminateLinearProgressIndicator()
+        )
+
+        nodeAssertion.assertExists()
+        // no error
+    }
+
+    @Test
+    fun isIndeterminateLinearProgressIndicator_noMatch_assertionError() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children += EmittableCircularProgressIndicator()
+            },
+            onNodeMatcher = isIndeterminateLinearProgressIndicator()
+        )
+
+        val assertionError = Assert.assertThrows(AssertionError::class.java) {
+            nodeAssertion.assertExists()
+        }
+
+        assertThat(assertionError)
+            .hasMessageThat()
+            .contains(
+                "Failed assertExists" +
+                    "\nReason: Expected '1' node(s) matching condition: " +
+                    "is an indeterminate linear progress indicator, but found '0'"
+            )
+    }
+
+    @Test
+    fun isLinearProgressIndicator_match() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children += EmittableLinearProgressIndicator().apply {
+                    progress = 10.0f
+                }
+            },
+            onNodeMatcher = isLinearProgressIndicator(10.0f)
+        )
+
+        nodeAssertion.assertExists()
+        // no error
+    }
+
+    @Test
+    fun isLinearProgressIndicator_progressValueNotMatch_assertionError() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children += EmittableLinearProgressIndicator().apply {
+                    indeterminate = false
+                    progress = 10.0f
+                }
+            },
+            onNodeMatcher = isLinearProgressIndicator(11.0f)
+        )
+
+        val assertionError = Assert.assertThrows(AssertionError::class.java) {
+            nodeAssertion.assertExists()
+        }
+
+        assertThat(assertionError)
+            .hasMessageThat()
+            .contains(
+                "Failed assertExists" +
+                    "\nReason: Expected '1' node(s) matching condition: " +
+                    "is a linear progress indicator with progress value: 11.0, but found '0'"
+            )
+    }
+}
diff --git a/glance/glance-appwidget/api/current.txt b/glance/glance-appwidget/api/current.txt
index e22d9a9..3444a5e 100644
--- a/glance/glance-appwidget/api/current.txt
+++ b/glance/glance-appwidget/api/current.txt
@@ -208,6 +208,21 @@
 
 }
 
+package androidx.glance.appwidget.component {
+
+  public final class ButtonsKt {
+    method @androidx.compose.runtime.Composable public static void CircleIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider? backgroundColor, optional androidx.glance.unit.ColorProvider contentColor);
+    method @androidx.compose.runtime.Composable public static void CircleIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider? backgroundColor, optional androidx.glance.unit.ColorProvider contentColor, optional String? key);
+    method @androidx.compose.runtime.Composable public static void FilledButton(String text, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional androidx.glance.ButtonColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void FilledButton(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional androidx.glance.ButtonColors colors, optional int maxLines, optional String? key);
+    method @androidx.compose.runtime.Composable public static void OutlineButton(String text, androidx.glance.unit.ColorProvider contentColor, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void OutlineButton(String text, androidx.glance.unit.ColorProvider contentColor, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional int maxLines, optional String? key);
+    method @androidx.compose.runtime.Composable public static void SquareIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider backgroundColor, optional androidx.glance.unit.ColorProvider contentColor);
+    method @androidx.compose.runtime.Composable public static void SquareIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider backgroundColor, optional androidx.glance.unit.ColorProvider contentColor, optional String? key);
+  }
+
+}
+
 package androidx.glance.appwidget.lazy {
 
   public abstract sealed class GridCells {
diff --git a/glance/glance-appwidget/api/restricted_current.txt b/glance/glance-appwidget/api/restricted_current.txt
index e22d9a9..3444a5e 100644
--- a/glance/glance-appwidget/api/restricted_current.txt
+++ b/glance/glance-appwidget/api/restricted_current.txt
@@ -208,6 +208,21 @@
 
 }
 
+package androidx.glance.appwidget.component {
+
+  public final class ButtonsKt {
+    method @androidx.compose.runtime.Composable public static void CircleIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider? backgroundColor, optional androidx.glance.unit.ColorProvider contentColor);
+    method @androidx.compose.runtime.Composable public static void CircleIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider? backgroundColor, optional androidx.glance.unit.ColorProvider contentColor, optional String? key);
+    method @androidx.compose.runtime.Composable public static void FilledButton(String text, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional androidx.glance.ButtonColors colors, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void FilledButton(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional androidx.glance.ButtonColors colors, optional int maxLines, optional String? key);
+    method @androidx.compose.runtime.Composable public static void OutlineButton(String text, androidx.glance.unit.ColorProvider contentColor, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional int maxLines);
+    method @androidx.compose.runtime.Composable public static void OutlineButton(String text, androidx.glance.unit.ColorProvider contentColor, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional int maxLines, optional String? key);
+    method @androidx.compose.runtime.Composable public static void SquareIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider backgroundColor, optional androidx.glance.unit.ColorProvider contentColor);
+    method @androidx.compose.runtime.Composable public static void SquareIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider backgroundColor, optional androidx.glance.unit.ColorProvider contentColor, optional String? key);
+  }
+
+}
+
 package androidx.glance.appwidget.lazy {
 
   public abstract sealed class GridCells {
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml b/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml
index 9107a59..8eb950b 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml
@@ -254,5 +254,32 @@
                 android:name="android.appwidget.provider"
                 android:resource="@xml/default_app_widget_info" />
         </receiver>
+
+        <receiver
+            android:name="androidx.glance.appwidget.demos.ButtonsWidgetBroadcastReceiver"
+            android:enabled="@bool/glance_appwidget_available"
+            android:exported="false"
+            android:label="@string/buttons_widget_name">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+                <action android:name="android.intent.action.LOCALE_CHANGED" />
+            </intent-filter>
+            <meta-data
+                android:name="android.appwidget.provider"
+                android:resource="@xml/default_app_widget_info" />
+        </receiver>
+
+        <receiver
+            android:name="androidx.glance.appwidget.demos.BackgroundTintWidgetBroadcastReceiver"
+            android:label="@string/tint_widget"
+            android:enabled="@bool/glance_appwidget_available"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+            </intent-filter>
+            <meta-data
+                android:name="android.appwidget.provider"
+                android:resource="@xml/default_app_widget_info" />
+        </receiver>
     </application>
 </manifest>
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/BackgroundTintWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/BackgroundTintWidget.kt
new file mode 100644
index 0000000..58f22cc
--- /dev/null
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/BackgroundTintWidget.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.glance.appwidget.demos
+
+import android.content.Context
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.glance.ColorFilter
+import androidx.glance.GlanceId
+import androidx.glance.GlanceModifier
+import androidx.glance.GlanceTheme
+import androidx.glance.ImageProvider
+import androidx.glance.appwidget.GlanceAppWidget
+import androidx.glance.appwidget.GlanceAppWidgetReceiver
+import androidx.glance.appwidget.SizeMode
+import androidx.glance.appwidget.provideContent
+import androidx.glance.background
+import androidx.glance.layout.Box
+import androidx.glance.layout.Column
+import androidx.glance.layout.size
+import androidx.glance.unit.ColorProvider
+
+class BackgroundTintWidgetBroadcastReceiver() : GlanceAppWidgetReceiver() {
+    override val glanceAppWidget: GlanceAppWidget
+        get() = BackgroundTintWidget()
+}
+
+/**
+ * Demonstrates tinting background drawables with [ColorFilter].
+ */
+class BackgroundTintWidget : GlanceAppWidget() {
+    override val sizeMode: SizeMode
+        get() = SizeMode.Exact
+
+    override suspend fun provideGlance(context: Context, id: GlanceId) {
+        provideContent {
+            GlanceTheme {
+                Column {
+                    Box(
+                        // Tint a <shape>
+                        modifier = GlanceModifier
+                            .size(width = 100.dp, height = 50.dp)
+                            .background(
+                                ImageProvider(R.drawable.shape_btn_demo),
+                                tint = ColorFilter.tint(GlanceTheme.colors.primary)
+                            ),
+                        content = {})
+                    Box(
+                        // tint an AVD
+                        modifier = GlanceModifier
+                            .size(width = 100.dp, height = 50.dp)
+                            .background(
+                                ImageProvider(R.drawable.ic_android),
+                                tint = ColorFilter.tint(ColorProvider(Color.Cyan))
+                            ),
+                        content = {}
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ButtonsWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ButtonsWidget.kt
new file mode 100644
index 0000000..a9d63be
--- /dev/null
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ButtonsWidget.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.glance.appwidget.demos
+
+import android.content.Context
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.glance.Button
+import androidx.glance.ButtonColors
+import androidx.glance.ButtonDefaults
+import androidx.glance.GlanceId
+import androidx.glance.GlanceModifier
+import androidx.glance.GlanceTheme
+import androidx.glance.ImageProvider
+import androidx.glance.appwidget.GlanceAppWidget
+import androidx.glance.appwidget.GlanceAppWidgetReceiver
+import androidx.glance.appwidget.SizeMode
+import androidx.glance.appwidget.component.CircleIconButton
+import androidx.glance.appwidget.component.FilledButton
+import androidx.glance.appwidget.component.OutlineButton
+import androidx.glance.appwidget.component.SquareIconButton
+import androidx.glance.appwidget.lazy.LazyColumn
+import androidx.glance.appwidget.lazy.LazyItemScope
+import androidx.glance.appwidget.lazy.LazyListScope
+import androidx.glance.appwidget.provideContent
+import androidx.glance.background
+import androidx.glance.layout.Alignment
+import androidx.glance.layout.Column
+import androidx.glance.layout.Row
+import androidx.glance.layout.Spacer
+import androidx.glance.layout.fillMaxSize
+import androidx.glance.layout.height
+import androidx.glance.layout.padding
+import androidx.glance.layout.size
+
+class ButtonsWidgetBroadcastReceiver() : GlanceAppWidgetReceiver() {
+
+    override val glanceAppWidget: GlanceAppWidget
+        get() = ButtonsWidget()
+}
+
+/**
+ * Demonstrates different button styles. Outline buttons will render as standard buttons on
+ * apis <31.
+ */
+class ButtonsWidget() : GlanceAppWidget() {
+    override val sizeMode: SizeMode
+        get() = SizeMode.Exact // one callback each time widget resized
+
+    @RequiresApi(Build.VERSION_CODES.S)
+    override suspend fun provideGlance(context: Context, id: GlanceId) {
+
+        provideContent {
+            val primary = GlanceTheme.colors.primary
+            val onPrimary = GlanceTheme.colors.onPrimary
+            val colors = ButtonDefaults.buttonColors(
+                backgroundColor = primary,
+                contentColor = onPrimary
+            )
+
+            LazyColumn(
+                modifier = GlanceModifier.fillMaxSize()
+                    .background(Color.DarkGray)
+                    .padding(16.dp)
+            ) {
+
+                paddedItem {
+                    Button(
+                        text = "Standard Button",
+                        onClick = {},
+                        modifier = GlanceModifier,
+                        colors = colors,
+                        maxLines = 1
+                    )
+                }
+
+                paddedItem {
+                    FilledButton(
+                        text = "Filled Button",
+                        colors = colors,
+                        modifier = GlanceModifier,
+                        onClick = {},
+                    )
+                }
+
+                paddedItem {
+                    FilledButton(
+                        text = "Filled Button",
+                        icon = ImageProvider(R.drawable.baseline_add_24),
+                        colors = colors,
+                        modifier = GlanceModifier,
+                        onClick = {},
+                    )
+                }
+
+                paddedItem {
+                    OutlineButton(
+                        text = "Outline Button",
+                        contentColor = primary,
+                        modifier = GlanceModifier,
+                        onClick = {},
+                    )
+                }
+
+                paddedItem {
+                    OutlineButton(
+                        text = "Outline Button",
+                        icon = ImageProvider(R.drawable.baseline_add_24),
+                        contentColor = primary,
+                        modifier = GlanceModifier,
+                        onClick = {},
+                    )
+                }
+
+                paddedItem {
+                    LongTextButtons(GlanceModifier, colors)
+                }
+
+                paddedItem {
+                    IconButtons()
+                }
+            } // end lazy column
+        }
+    }
+}
+
+private fun LazyListScope.paddedItem(content: @Composable LazyItemScope.() -> Unit) {
+    this.item {
+        Column {
+            content()
+            Space()
+        }
+    }
+}
+
+@Composable
+private fun LongTextButtons(modifier: GlanceModifier, colors: ButtonColors) {
+    Row(modifier = modifier) {
+        FilledButton(
+            text = "Three\nLines\nof text",
+            icon = ImageProvider(R.drawable.baseline_add_24),
+            colors = colors,
+            modifier = GlanceModifier,
+            onClick = {},
+        )
+
+        Space()
+
+        FilledButton(
+            text = "Two\nLines\nof text",
+            icon = ImageProvider(R.drawable.baseline_add_24),
+            colors = colors,
+            modifier = GlanceModifier,
+            onClick = {},
+            maxLines = 2
+        )
+    }
+}
+
+@Composable
+private fun IconButtons() {
+    Row(
+        modifier = GlanceModifier.height(80.dp).padding(vertical = 8.dp),
+        verticalAlignment = Alignment.Vertical.CenterVertically
+    ) {
+        SquareIconButton(
+            imageProvider = ImageProvider(R.drawable.baseline_add_24),
+            contentDescription = "Add Button",
+            onClick = { }
+        )
+        Space()
+
+        CircleIconButton(
+            imageProvider = ImageProvider(R.drawable.baseline_local_phone_24),
+            contentDescription = "Call Button",
+            backgroundColor = GlanceTheme.colors.surfaceVariant,
+            contentColor = GlanceTheme.colors.onSurfaceVariant,
+            onClick = { }
+        )
+        Space()
+
+        CircleIconButton(
+            imageProvider = ImageProvider(R.drawable.baseline_local_phone_24),
+            contentDescription = "Call Button",
+            backgroundColor = null, // empty background
+            contentColor = GlanceTheme.colors.primary,
+            onClick = { }
+        )
+    }
+}
+
+@Composable
+private fun Space() = Spacer(GlanceModifier.size(8.dp))
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/baseline_add_24.xml b/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/baseline_add_24.xml
new file mode 100644
index 0000000..03d6cd3
--- /dev/null
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/baseline_add_24.xml
@@ -0,0 +1,21 @@
+<!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector android:height="24dp" android:tint="#000000"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+</vector>
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/baseline_local_phone_24.xml b/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/baseline_local_phone_24.xml
new file mode 100644
index 0000000..95c5f8a
--- /dev/null
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/baseline_local_phone_24.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#000000"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/>
+</vector>
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/shape_btn_demo.xml b/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/shape_btn_demo.xml
new file mode 100644
index 0000000..12bdd47
--- /dev/null
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/shape_btn_demo.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <stroke android:color="#FF00FF"/>
+    <corners android:radius="16dp"/>
+
+</shape>
\ No newline at end of file
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml b/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml
index e74682e..04943bf 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml
@@ -37,5 +37,6 @@
     <string name="default_state_widget_name">Default State Widget</string>
     <string name="progress_indicator_widget_name">ProgressBar Widget</string>
     <string name="default_color_widget_name">Theme and Color Widget</string>
-
+    <string name="buttons_widget_name">Buttons Demo Widget</string>
+    <string name="tint_widget">Tint widget</string>
 </resources>
diff --git a/glance/glance-appwidget/lint-baseline.xml b/glance/glance-appwidget/lint-baseline.xml
index 87160dc..79615c9 100644
--- a/glance/glance-appwidget/lint-baseline.xml
+++ b/glance/glance-appwidget/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.2.0-alpha14" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-alpha14)" variant="all" version="8.2.0-alpha14">
+<issues format="6" by="lint 8.2.0-alpha15" type="baseline" client="cli" dependencies="false" name="AGP (8.2.0-alpha15)" variant="all" version="8.2.0-alpha15">
 
     <issue
         id="BanThreadSleep"
@@ -29,6 +29,465 @@
     </issue>
 
     <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        it.children.addAll(children.map { it.copy() })"
+        errorLine2="                                    ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/AndroidRemoteViews.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="                    lambdas[event.key]?.forEach { it.block() }"
+        errorLine2="                                        ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/AppWidgetSession.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        sizes.map { DpSize(it.width.dp, it.height.dp) }"
+        errorLine2="              ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/AppWidgetUtils.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    }.minByOrNull { it.second }?.first"
+        errorLine2="      ~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/AppWidgetUtils.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            rv.setContentDescription(viewDef.mainViewId, contentDescription.joinToString())"
+        errorLine2="                                                                            ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/ApplyModifiers.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    manager.getGlanceIds(javaClass).forEach { update(context, it) }"
+        errorLine2="                                    ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    manager.getGlanceIds(javaClass).forEach { glanceId ->"
+        errorLine2="                                    ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            }.toMap()"
+        errorLine2="              ~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        return receivers.flatMap { receiver ->"
+        errorLine2="                         ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="                    val info = appWidgetManager.installedProviders.first {"
+        errorLine2="                                                                   ~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            .filter { it.provider.packageName == packageName }"
+        errorLine2="             ~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            .map { it.provider.className }"
+        errorLine2="             ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            .toSet()"
+        errorLine2="             ~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="                toRemove.forEach { receiver -> remove(providerKey(receiver)) }"
+        errorLine2="                         ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        if (children.any { it.shouldIgnoreResult() }) return true"
+        errorLine2="                     ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/IgnoreResult.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        .forEach {"
+        errorLine2="         ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/LayoutSelection.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        itemList.forEachIndexed { index, (itemId, composable) ->"
+        errorLine2="                 ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/lazy/LazyList.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        it.children.addAll(children.map { it.copy() })"
+        errorLine2="                                    ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/lazy/LazyList.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        it.children.addAll(children.map { it.copy() })"
+        errorLine2="                                    ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/lazy/LazyList.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        element.children.foldIndexed(false) { position, previous, itemEmittable ->"
+        errorLine2="                         ~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/translators/LazyListTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        itemList.forEachIndexed { index, (itemId, composable) ->"
+        errorLine2="                 ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        it.children.addAll(children.map { it.copy() })"
+        errorLine2="                                    ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        it.children.addAll(children.map { it.copy() })"
+        errorLine2="                                    ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        element.children.foldIndexed(false) { position, previous, itemEmittable ->"
+        errorLine2="                         ~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/translators/LazyVerticalGridTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    if (container.children.isNotEmpty() &amp;&amp; container.children.all { it is EmittableSizeBox }) {"
+        errorLine2="                                                              ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        for (item in container.children) {"
+        errorLine2="                  ~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    children.forEach { child ->"
+        errorLine2="             ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        children.any { child ->"
+        errorLine2="                 ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        children.any { child ->"
+        errorLine2="                 ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    children.forEachIndexed { index, child ->"
+        errorLine2="             ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    children.foldIndexed("
+        errorLine2="             ~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    fold(GlanceModifier) { acc: GlanceModifier, mod: GlanceModifier? ->"
+        errorLine2="    ~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        val layoutIdCount = views.map { it.layoutId }.distinct().count()"
+        errorLine2="                                                      ~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/RemoteCollectionItems.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="                viewTypeCount = views.map { it.layoutId }.distinct().count()"
+        errorLine2="                                      ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/RemoteCollectionItems.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="                viewTypeCount = views.map { it.layoutId }.distinct().count()"
+        errorLine2="                                                          ~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/RemoteCollectionItems.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        it.children.addAll(children.map { it.copy() })"
+        errorLine2="                                    ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/RemoteViewsRoot.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    if (children.all { it is EmittableSizeBox }) {"
+        errorLine2="                 ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        val views = children.map { child ->"
+        errorLine2="                             ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="                    Api31Impl.createRemoteViews(views.toMap())"
+        errorLine2="                                                      ~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="                    combineLandscapeAndPortrait(views.map { it.second })"
+        errorLine2="                                                      ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    element.children.forEach {"
+        errorLine2="                     ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    check(children.count { it is EmittableRadioButton &amp;&amp; it.checked } &lt;= 1) {"
+        errorLine2="                   ~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            element.children.forEachIndexed { index, child ->"
+        errorLine2="                             ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    children.take(10).forEachIndexed { index, child ->"
+        errorLine2="             ~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    children.take(10).forEachIndexed { index, child ->"
+        errorLine2="                      ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        it.children.addAll(children.map { it.copy() })"
+        errorLine2="                                    ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/SizeBox.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="                .map { findBestSize(it, sizeMode.sizes) ?: smallestSize }"
+        errorLine2="                 ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/SizeBox.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    sizes.distinct().map { size ->"
+        errorLine2="                     ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/SizeBox.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    spans.forEach { span ->"
+        errorLine2="          ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/translators/TextTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            val layouts = config.layoutList.associate {"
+        errorLine2="                                            ~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/WidgetLayout.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            addAllChildren(element.children.map { createNode(context, it) })"
+        errorLine2="                                            ~~~">
+        <location
+            file="src/main/java/androidx/glance/appwidget/WidgetLayout.kt"/>
+    </issue>
+
+    <issue
         id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method extractAllSizes has parameter &apos;minSize&apos; with type Function0&lt;DpSize>."
         errorLine1="internal fun Bundle.extractAllSizes(minSize: () -> DpSize): List&lt;DpSize> {"
diff --git a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/AppWidgetHostTestActivity.kt b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/AppWidgetHostTestActivity.kt
index f42506b..5b14889 100644
--- a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/AppWidgetHostTestActivity.kt
+++ b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/AppWidgetHostTestActivity.kt
@@ -175,7 +175,8 @@
     }
 
     private var mLatch: CountDownLatch? = null
-    private var mRemoteViews: RemoteViews? = null
+    var mRemoteViews: RemoteViews? = null
+        private set
     private var mPortraitSize: DpSize = DpSize(0.dp, 0.dp)
     private var mLandscapeSize: DpSize = DpSize(0.dp, 0.dp)
 
diff --git a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverScreenshotTest.kt b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverScreenshotTest.kt
index ffb947e..fb842fb 100644
--- a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverScreenshotTest.kt
+++ b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverScreenshotTest.kt
@@ -26,14 +26,20 @@
 import androidx.glance.Button
 import androidx.glance.ButtonDefaults
 import androidx.glance.GlanceModifier
+import androidx.glance.GlanceTheme
 import androidx.glance.Image
 import androidx.glance.ImageProvider
 import androidx.glance.LocalContext
 import androidx.glance.action.actionStartActivity
+import androidx.glance.appwidget.component.CircleIconButton
+import androidx.glance.appwidget.component.FilledButton
+import androidx.glance.appwidget.component.OutlineButton
+import androidx.glance.appwidget.component.SquareIconButton
 import androidx.glance.appwidget.lazy.LazyColumn
 import androidx.glance.appwidget.test.R
 import androidx.glance.background
 import androidx.glance.color.ColorProvider
+import androidx.glance.color.colorProviders
 import androidx.glance.layout.Alignment
 import androidx.glance.layout.Box
 import androidx.glance.layout.Column
@@ -589,6 +595,43 @@
         mHostRule.waitForListViewChildCount(count)
         mScreenshotRule.checkScreenshot(mHostRule.mHostView, "lazyColumn_alignment_start")
     }
+
+    @Test
+    fun buttonTests_createFilledButton() {
+        TestGlanceAppWidget.uiDefinition = { ButtonComponentsScreenshotTests.FilledButtonTest() }
+        mHostRule.startHost()
+        mScreenshotRule.checkScreenshot(mHostRule.mHostView, "buttonTests_createFilledButton")
+    }
+
+    @Test
+    fun buttonTests_createOutlineButton() {
+        TestGlanceAppWidget.uiDefinition = { ButtonComponentsScreenshotTests.OutlineButtonTest() }
+        mHostRule.startHost()
+        mScreenshotRule.checkScreenshot(mHostRule.mHostView, "buttonTests_createOutlineButton")
+    }
+
+    @Test
+    fun buttonTests_createSquareButton() {
+        TestGlanceAppWidget.uiDefinition = { ButtonComponentsScreenshotTests.SquareButtonTest() }
+        mHostRule.startHost()
+        mScreenshotRule.checkScreenshot(mHostRule.mHostView, "buttonTests_createSquareButton")
+    }
+
+    @Test
+    fun buttonTests_createCircleButton() {
+        TestGlanceAppWidget.uiDefinition = { ButtonComponentsScreenshotTests.CircleButtonTest() }
+        mHostRule.startHost()
+        mScreenshotRule.checkScreenshot(mHostRule.mHostView, "buttonTests_createCircleButton")
+    }
+
+    @Test
+    fun buttonTests_buttonDefaultColors() {
+        TestGlanceAppWidget.uiDefinition = {
+            ButtonComponentsScreenshotTests.ButtonDefaultColorsTest()
+        }
+        mHostRule.startHost()
+        mScreenshotRule.checkScreenshot(mHostRule.mHostView, "buttonTests_buttonDefaultColors")
+    }
 }
 
 @Composable
@@ -872,3 +915,166 @@
         )
     }
 }
+
+// Tests the opinionated button components
+private object ButtonComponentsScreenshotTests {
+
+    private val onClick: () -> Unit = {}
+
+    // a filled square
+    private val icon = ImageProvider(R.drawable.filled_oval)
+
+    private val buttonBg = ColorProvider(Color.Black)
+    private val buttonFg = ColorProvider(Color.White)
+
+    @Composable
+    private fun colors() = ButtonDefaults.buttonColors(
+        backgroundColor = buttonBg,
+        contentColor = buttonFg
+    )
+
+    @Composable
+    private fun Space() = Spacer(GlanceModifier.size(16.dp))
+
+    /**
+     * A rectangular magenta background
+     */
+    @Composable
+    private fun Background(content: @Composable () -> Unit) {
+        Box(
+            modifier = GlanceModifier.wrapContentSize().padding(16.dp).background(Color.Magenta),
+            content = content
+        )
+    }
+
+    @Composable
+    fun FilledButtonTest() {
+        Background {
+            Column {
+                FilledButton(
+                    text = "Filled button\nbg 0x00, fg 0xff",
+                    onClick = onClick,
+                    icon = null,
+                    colors = colors()
+                )
+                Space()
+                FilledButton(
+                    text = "Filled btn + icon\nbg 0x00, fg 0xff",
+                    onClick = onClick,
+                    icon = icon,
+                    colors = colors()
+                )
+            }
+        }
+    }
+
+    @Composable
+    fun OutlineButtonTest() {
+        Background {
+            Column {
+                OutlineButton(
+                    text = "Outline Button\nfg 0xff",
+                    onClick = onClick,
+                    icon = null,
+                    contentColor = buttonFg
+                )
+                Space()
+                OutlineButton(
+                    text = "Outline btn + icon\nfg 0xff",
+                    onClick = onClick,
+                    icon = icon,
+                    contentColor = buttonFg
+                )
+            }
+        }
+    }
+
+    @Composable
+    fun SquareButtonTest() {
+        Background {
+            // square button with rounded corners, icon (a square w/sharp corners)  in center.
+            SquareIconButton(
+                imageProvider = icon,
+                contentDescription = null,
+                onClick = onClick,
+                backgroundColor = buttonBg,
+                contentColor = buttonFg
+            )
+        }
+    }
+
+    @Composable
+    fun CircleButtonTest() {
+        Background {
+            Column {
+                // Circle button with icon
+                CircleIconButton(
+                    imageProvider = icon,
+                    contentDescription = null,
+                    onClick = onClick,
+                    backgroundColor = buttonBg,
+                    contentColor = buttonFg
+                )
+                Space()
+                // Icon only, no background
+                CircleIconButton(
+                    imageProvider = icon,
+                    contentDescription = null,
+                    onClick = onClick,
+                    backgroundColor = null,
+                    contentColor = buttonFg
+                )
+            }
+        }
+    }
+
+    /**
+     * Tests that buttons inherit the expected colors from their theme.
+     */
+    @Composable
+    fun ButtonDefaultColorsTest() {
+        val unused = ColorProvider(Color.Cyan)
+
+        val colors = colorProviders(
+            primary = ColorProvider(Color.Green),
+            onPrimary = ColorProvider(Color.Black),
+            surface = ColorProvider(Color.Gray),
+            onSurface = ColorProvider(Color.Red),
+            background = ColorProvider(Color.DarkGray),
+            error = unused,
+            errorContainer = unused,
+            inverseOnSurface = unused,
+            inversePrimary = unused,
+            inverseSurface = unused,
+            onBackground = unused,
+            onError = unused,
+            onErrorContainer = unused,
+            onPrimaryContainer = unused,
+            onSecondary = unused,
+            onSecondaryContainer = unused,
+            onSurfaceVariant = unused,
+            onTertiary = unused,
+            onTertiaryContainer = unused,
+            outline = unused,
+            primaryContainer = unused,
+            secondary = unused,
+            secondaryContainer = unused,
+            surfaceVariant = unused,
+            tertiary = unused,
+            tertiaryContainer = unused,
+        )
+
+        GlanceTheme(
+            colors = colors
+        ) {
+            Column {
+                FilledButton("Filled button", icon = icon, onClick = onClick)
+                // [OutlineButton] does not have a default color, so not important to test here
+                Space()
+                SquareIconButton(imageProvider = icon, contentDescription = null, onClick = onClick)
+                Space()
+                CircleIconButton(imageProvider = icon, contentDescription = null, onClick = onClick)
+            }
+        }
+    }
+}
diff --git a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt
index 22f4460..8a03aa3 100644
--- a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt
+++ b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt
@@ -40,6 +40,7 @@
 import android.widget.TextView
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -64,6 +65,7 @@
 import androidx.glance.action.actionStartActivity
 import androidx.glance.action.clickable
 import androidx.glance.action.toParametersKey
+import androidx.glance.appwidget.R.layout.glance_error_layout
 import androidx.glance.appwidget.action.ActionCallback
 import androidx.glance.appwidget.action.ToggleableStateKey
 import androidx.glance.appwidget.action.actionRunCallback
@@ -1192,6 +1194,83 @@
         }
     }
 
+    @Test
+    fun initialCompositionErrorUiLayout() = runBlocking {
+        TestGlanceAppWidget.withErrorLayout(glance_error_layout) {
+            TestGlanceAppWidget.uiDefinition = {
+                 throw Throwable("error")
+            }
+
+            mHostRule.startHost()
+            mHostRule.onHostView { hostView ->
+                val layoutId = assertNotNull(
+                    (hostView as TestAppWidgetHostView).mRemoteViews?.layoutId
+                )
+                assertThat(layoutId).isEqualTo(glance_error_layout)
+            }
+        }
+    }
+
+    @Test
+    fun recompositionErrorUiLayout() = runBlocking {
+        TestGlanceAppWidget.withErrorLayout(glance_error_layout) {
+            val runError = mutableStateOf(false)
+            TestGlanceAppWidget.uiDefinition = {
+                if (runError.value)
+                    throw Throwable("error")
+                else Text("Hello World")
+            }
+
+            mHostRule.startHost()
+            mHostRule.onUnboxedHostView<TextView> {
+                assertThat(it.text.toString()).isEqualTo("Hello World")
+            }
+            mHostRule.runAndWaitForUpdate { runError.value = true }
+            mHostRule.onHostView { hostView ->
+                val layoutId = assertNotNull(
+                    (hostView as TestAppWidgetHostView).mRemoteViews?.layoutId
+                )
+                assertThat(layoutId).isEqualTo(glance_error_layout)
+            }
+        }
+    }
+
+    @Test
+    fun sideEffectErrorUiLayout() = runBlocking {
+        TestGlanceAppWidget.withErrorLayout(glance_error_layout) {
+            TestGlanceAppWidget.uiDefinition = {
+                SideEffect { throw Throwable("error") }
+            }
+
+            mHostRule.startHost()
+            mHostRule.onHostView { hostView ->
+                val layoutId = assertNotNull(
+                    (hostView as TestAppWidgetHostView).mRemoteViews?.layoutId
+                )
+                assertThat(layoutId).isEqualTo(glance_error_layout)
+            }
+        }
+    }
+
+    @Test
+    fun provideGlanceErrorUiLayout() = runBlocking {
+        // This also tests LaunchedEffect error handling, since provideGlance is run in a
+        // LaunchedEffect through collectAsState.
+        TestGlanceAppWidget.withErrorLayout(glance_error_layout) {
+            TestGlanceAppWidget.onProvideGlance = {
+                throw Throwable("error")
+            }
+
+            mHostRule.startHost()
+            mHostRule.onHostView { hostView ->
+                val layoutId = assertNotNull(
+                    (hostView as TestAppWidgetHostView).mRemoteViews?.layoutId
+                )
+                assertThat(layoutId).isEqualTo(glance_error_layout)
+            }
+        }
+    }
+
     // Check there is a single span of the given type and that it passes the [check].
     private inline
     fun <reified T> SpannedString.checkHasSingleTypedSpan(check: (T) -> Unit) {
diff --git a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/TestGlanceAppWidgetReceiver.kt b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/TestGlanceAppWidgetReceiver.kt
index e0865a3..ab29527 100644
--- a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/TestGlanceAppWidgetReceiver.kt
+++ b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/TestGlanceAppWidgetReceiver.kt
@@ -41,7 +41,8 @@
     }
 }
 
-object TestGlanceAppWidget : GlanceAppWidget(errorUiLayout = 0) {
+object TestGlanceAppWidget : GlanceAppWidget() {
+    public override var errorUiLayout: Int = 0
 
     override var sizeMode: SizeMode = SizeMode.Single
 
@@ -49,9 +50,12 @@
         context: Context,
         id: GlanceId
     ) {
-        onProvideGlance?.invoke(this)
-        onProvideGlance = null
-        provideContent(uiDefinition)
+        try {
+            onProvideGlance?.invoke(this)
+                ?: provideContent(uiDefinition)
+        } finally {
+          onProvideGlance = null
+        }
     }
 
     var onProvideGlance: (suspend TestGlanceAppWidget.() -> Unit)? = null
@@ -71,4 +75,14 @@
     }
 
     var uiDefinition: @Composable () -> Unit = { }
+
+    inline fun withErrorLayout(layout: Int, block: () -> Unit) {
+        val previousErrorLayout = errorUiLayout
+        errorUiLayout = layout
+        try {
+            block()
+        } finally {
+            errorUiLayout = previousErrorLayout
+        }
+    }
 }
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AndroidRemoteViews.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AndroidRemoteViews.kt
index 8450bff..f199042 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AndroidRemoteViews.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AndroidRemoteViews.kt
@@ -20,7 +20,6 @@
 import android.widget.RemoteViews
 import androidx.annotation.IdRes
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.util.fastMap
 import androidx.glance.Emittable
 import androidx.glance.EmittableWithChildren
 import androidx.glance.GlanceModifier
@@ -80,7 +79,7 @@
             it.remoteViews = remoteViews
         }
         it.containerViewId = containerViewId
-        it.children.addAll(children.fastMap { it.copy() })
+        it.children.addAll(children.map { it.copy() })
     }
 
     override fun toString(): String = "AndroidRemoteViews(" +
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt
index 314e095..859c7cac 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt
@@ -32,7 +32,6 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.unit.DpSize
-import androidx.compose.ui.util.fastForEach
 import androidx.glance.EmittableWithChildren
 import androidx.glance.GlanceComposable
 import androidx.glance.LocalContext
@@ -144,13 +143,7 @@
         } catch (ex: CancellationException) {
             // Nothing to do
         } catch (throwable: Throwable) {
-            if (widget.errorUiLayout == 0) {
-                throw throwable
-            }
-            logException(throwable)
-            val rv = RemoteViews(context.packageName, widget.errorUiLayout)
-            appWidgetManager.updateAppWidget(id.appWidgetId, rv)
-            lastRemoteViews = rv
+            sendErrorLayoutIfPresent(context, throwable)
         } finally {
             layoutConfig.save()
             Tracing.endGlanceAppWidgetUpdate()
@@ -158,6 +151,10 @@
         return true
     }
 
+    override suspend fun onCompositionError(context: Context, throwable: Throwable) {
+        sendErrorLayoutIfPresent(context, throwable)
+    }
+
     override suspend fun processEvent(context: Context, event: Any) {
         when (event) {
             is UpdateGlanceState -> {
@@ -184,7 +181,7 @@
             is RunLambda -> {
                 if (DEBUG) Log.i(TAG, "Received RunLambda(${event.key}) action for session($key)")
                 Snapshot.withMutableSnapshot {
-                    lambdas[event.key]?.fastForEach { it.block() }
+                    lambdas[event.key]?.forEach { it.block() }
                 } ?: Log.w(TAG, "Triggering Action(${event.key}) for session($key) failed")
             }
             is WaitForReady -> event.resume.send(Unit)
@@ -215,6 +212,16 @@
         }
     }
 
+    private fun sendErrorLayoutIfPresent(context: Context, throwable: Throwable) {
+        if (widget.errorUiLayout == 0) {
+            throw throwable
+        }
+        logException(throwable)
+        val rv = RemoteViews(context.packageName, widget.errorUiLayout)
+        context.appWidgetManager.updateAppWidget(id.appWidgetId, rv)
+        lastRemoteViews = rv
+    }
+
     // Event types that this session supports.
     @VisibleForTesting
     internal object UpdateGlanceState
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetUtils.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetUtils.kt
index 514a998..1b39006 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetUtils.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetUtils.kt
@@ -32,8 +32,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastMap
-import androidx.compose.ui.util.fastMinByOrNull
 import androidx.glance.GlanceComposable
 import androidx.glance.GlanceId
 import java.util.concurrent.atomic.AtomicBoolean
@@ -87,7 +85,7 @@
     return if (sizes.isNullOrEmpty()) {
         estimateSizes(minSize)
     } else {
-        sizes.fastMap { DpSize(it.width.dp, it.height.dp) }
+        sizes.map { DpSize(it.width.dp, it.height.dp) }
     }
 }
 
@@ -145,7 +143,7 @@
         } else {
             null
         }
-    }.fastMinByOrNull { it.second }?.first
+    }.minByOrNull { it.second }?.first
 
 /**
  * @return the minimum size as configured by the App Widget provider.
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/ApplyModifiers.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/ApplyModifiers.kt
index 5fb7402..0c43fc1 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/ApplyModifiers.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/ApplyModifiers.kt
@@ -28,7 +28,6 @@
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.util.fastJoinToString
 import androidx.core.widget.RemoteViewsCompat.setTextViewHeight
 import androidx.core.widget.RemoteViewsCompat.setTextViewWidth
 import androidx.core.widget.RemoteViewsCompat.setViewBackgroundColor
@@ -80,28 +79,33 @@
                 }
                 actionModifier = modifier
             }
+
             is WidthModifier -> widthModifier = modifier
             is HeightModifier -> heightModifier = modifier
             is BackgroundModifier -> applyBackgroundModifier(context, rv, modifier, viewDef)
             is PaddingModifier -> {
                 paddingModifiers = paddingModifiers?.let { it + modifier } ?: modifier
             }
+
             is VisibilityModifier -> visibility = modifier.visibility
             is CornerRadiusModifier -> cornerRadius = modifier.radius
             is AppWidgetBackgroundModifier -> {
                 // This modifier is handled somewhere else.
             }
+
             is SelectableGroupModifier -> {
                 if (!translationContext.canUseSelectableGroup) {
                     error(
                         "GlanceModifier.selectableGroup() can only be used on Row or Column " +
-                        "composables."
+                            "composables."
                     )
                 }
             }
+
             is AlignmentModifier -> {
                 // This modifier is handled somewhere else.
             }
+
             is ClipToOutlineModifier -> clipToOutline = modifier
             is EnabledModifier -> enabled = modifier
             is SemanticsModifier -> semanticsModifier = modifier
@@ -136,7 +140,7 @@
         val contentDescription: List<String>? =
             semantics.configuration.getOrNull(SemanticsProperties.ContentDescription)
         if (contentDescription != null) {
-            rv.setContentDescription(viewDef.mainViewId, contentDescription.fastJoinToString())
+            rv.setContentDescription(viewDef.mainViewId, contentDescription.joinToString())
         }
     }
     rv.setViewVisibility(viewDef.mainViewId, visibility.toViewVisibility())
@@ -227,7 +231,8 @@
     }
     // Wrap and Expand are done in XML on Android S & Sv2
     if (Build.VERSION.SDK_INT < 33 &&
-        width in listOf(Dimension.Wrap, Dimension.Expand)) return
+        width in listOf(Dimension.Wrap, Dimension.Expand)
+    ) return
     ApplyModifiersApi31Impl.setViewWidth(rv, viewId, width)
 }
 
@@ -257,7 +262,8 @@
     }
     // Wrap and Expand are done in XML on Android S & Sv2
     if (Build.VERSION.SDK_INT < 33 &&
-        height in listOf(Dimension.Wrap, Dimension.Expand)) return
+        height in listOf(Dimension.Wrap, Dimension.Expand)
+    ) return
     ApplyModifiersApi31Impl.setViewHeight(rv, viewId, height)
 }
 
@@ -268,8 +274,9 @@
     viewDef: InsertedViewInfo
 ) {
     val viewId = viewDef.mainViewId
-    val imageProvider = modifier.imageProvider
-    if (imageProvider != null) {
+
+    fun applyBackgroundImageModifier(modifier: BackgroundModifier.Image) {
+        val imageProvider = modifier.imageProvider
         if (imageProvider is AndroidResourceImageProvider) {
             rv.setViewBackgroundResource(viewId, imageProvider.resId)
         }
@@ -277,24 +284,41 @@
         // (removing modifiers is not really possible).
         return
     }
-    when (val colorProvider = modifier.colorProvider) {
-        is FixedColorProvider -> rv.setViewBackgroundColor(viewId, colorProvider.color.toArgb())
-        is ResourceColorProvider -> rv.setViewBackgroundColorResource(
-            viewId,
-            colorProvider.resId
-        )
-        is DayNightColorProvider -> {
-            if (Build.VERSION.SDK_INT >= 31) {
-                rv.setViewBackgroundColor(
-                    viewId,
-                    colorProvider.day.toArgb(),
-                    colorProvider.night.toArgb()
-                )
-            } else {
-                rv.setViewBackgroundColor(viewId, colorProvider.getColor(context).toArgb())
+
+    fun applyBackgroundColorModifier(modifier: BackgroundModifier.Color) {
+        when (val colorProvider = modifier.colorProvider) {
+            is FixedColorProvider -> rv.setViewBackgroundColor(
+                viewId,
+                colorProvider.color.toArgb()
+            )
+
+            is ResourceColorProvider -> rv.setViewBackgroundColorResource(
+                viewId,
+                colorProvider.resId
+            )
+
+            is DayNightColorProvider -> {
+                if (Build.VERSION.SDK_INT >= 31) {
+                    rv.setViewBackgroundColor(
+                        viewId,
+                        colorProvider.day.toArgb(),
+                        colorProvider.night.toArgb()
+                    )
+                } else {
+                    rv.setViewBackgroundColor(viewId, colorProvider.getColor(context).toArgb())
+                }
             }
+
+            else -> Log.w(
+                GlanceAppWidgetTag,
+                "Unexpected background color modifier: $colorProvider"
+            )
         }
-        else -> Log.w(GlanceAppWidgetTag, "Unexpected background color modifier: $colorProvider")
+    }
+
+    when (modifier) {
+        is BackgroundModifier.Image -> applyBackgroundImageModifier(modifier)
+        is BackgroundModifier.Color -> applyBackgroundColorModifier(modifier)
     }
 }
 
@@ -320,6 +344,7 @@
             is Dimension.Wrap -> {
                 rv.setViewLayoutWidth(viewId, WRAP_CONTENT.toFloat(), COMPLEX_UNIT_PX)
             }
+
             is Dimension.Expand -> rv.setViewLayoutWidth(viewId, 0f, COMPLEX_UNIT_PX)
             is Dimension.Dp -> rv.setViewLayoutWidth(viewId, width.dp.value, COMPLEX_UNIT_DIP)
             is Dimension.Resource -> rv.setViewLayoutWidthDimen(viewId, width.res)
@@ -335,6 +360,7 @@
             is Dimension.Wrap -> {
                 rv.setViewLayoutHeight(viewId, WRAP_CONTENT.toFloat(), COMPLEX_UNIT_PX)
             }
+
             is Dimension.Expand -> rv.setViewLayoutHeight(viewId, 0f, COMPLEX_UNIT_PX)
             is Dimension.Dp -> rv.setViewLayoutHeight(viewId, height.dp.value, COMPLEX_UNIT_DIP)
             is Dimension.Resource -> rv.setViewLayoutHeightDimen(viewId, height.res)
@@ -351,9 +377,11 @@
             is Dimension.Dp -> {
                 rv.setViewOutlinePreferredRadius(viewId, radius.dp.value, COMPLEX_UNIT_DIP)
             }
+
             is Dimension.Resource -> {
                 rv.setViewOutlinePreferredRadiusDimen(viewId, radius.res)
             }
+
             else -> error("Rounded corners should not be ${radius.javaClass.canonicalName}")
         }
     }
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CircularProgressIndicator.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CircularProgressIndicator.kt
index 9e13d34..bf622db 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CircularProgressIndicator.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/CircularProgressIndicator.kt
@@ -16,6 +16,7 @@
 
 package androidx.glance.appwidget
 
+import androidx.annotation.RestrictTo
 import androidx.compose.runtime.Composable
 import androidx.glance.Emittable
 import androidx.glance.GlanceModifier
@@ -42,7 +43,8 @@
     )
 }
 
-internal class EmittableCircularProgressIndicator : Emittable {
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class EmittableCircularProgressIndicator : Emittable {
     override var modifier: GlanceModifier = GlanceModifier
     var color: ColorProvider = ProgressIndicatorDefaults.IndicatorColorProvider
 
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt
index 2b0d9fa..c5f713f 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt
@@ -26,7 +26,6 @@
 import androidx.annotation.RestrictTo
 import androidx.annotation.RestrictTo.Scope
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.util.fastForEach
 import androidx.glance.GlanceComposable
 import androidx.glance.GlanceId
 import androidx.glance.appwidget.state.getAppWidgetState
@@ -50,7 +49,7 @@
  */
 abstract class GlanceAppWidget(
     @LayoutRes
-    internal val errorUiLayout: Int = R.layout.glance_error_layout,
+    internal open val errorUiLayout: Int = R.layout.glance_error_layout,
 ) {
     private val sessionManager: SessionManager = GlanceSessionManager
 
@@ -203,7 +202,7 @@
 /** Update all App Widgets managed by the [GlanceAppWidget] class. */
 suspend fun GlanceAppWidget.updateAll(@Suppress("ContextFirst") context: Context) {
     val manager = GlanceAppWidgetManager(context)
-    manager.getGlanceIds(javaClass).fastForEach { update(context, it) }
+    manager.getGlanceIds(javaClass).forEach { update(context, it) }
 }
 
 /**
@@ -216,7 +215,7 @@
     val stateDef = stateDefinition
     requireNotNull(stateDef) { "GlanceAppWidget.updateIf cannot be used if no state is defined." }
     val manager = GlanceAppWidgetManager(context)
-    manager.getGlanceIds(javaClass).fastForEach { glanceId ->
+    manager.getGlanceIds(javaClass).forEach { glanceId ->
         val state = getAppWidgetState(context, stateDef, glanceId) as State
         if (predicate(state)) update(context, glanceId)
     }
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt
index 089f713..41c7633 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidgetManager.kt
@@ -27,11 +27,6 @@
 import androidx.annotation.RequiresApi
 import androidx.annotation.VisibleForTesting
 import androidx.compose.ui.unit.DpSize
-import androidx.compose.ui.util.fastFilter
-import androidx.compose.ui.util.fastFirst
-import androidx.compose.ui.util.fastFlatMap
-import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastMap
 import androidx.datastore.core.DataStore
 import androidx.datastore.preferences.core.Preferences
 import androidx.datastore.preferences.core.stringPreferencesKey
@@ -89,7 +84,6 @@
         val packageName = context.packageName
         val receivers = prefs[providersKey] ?: return State()
         return State(
-            @Suppress("ListIterator")
             receivers.mapNotNull { receiverName ->
                 val comp = ComponentName(packageName, receiverName)
                 val providerName = prefs[providerKey(receiverName)] ?: return@mapNotNull null
@@ -108,7 +102,7 @@
         val state = getState()
         val providerName = requireNotNull(provider.canonicalName) { "no canonical provider name" }
         val receivers = state.providerNameToReceivers[providerName] ?: return emptyList()
-        return receivers.fastFlatMap { receiver ->
+        return receivers.flatMap { receiver ->
             appWidgetManager.getAppWidgetIds(receiver).map { AppWidgetId(it) }
         }
     }
@@ -202,7 +196,7 @@
             val target = ComponentName(context.packageName, receiver.name)
             val previewBundle = Bundle().apply {
                 if (preview != null) {
-                    val info = appWidgetManager.installedProviders.fastFirst {
+                    val info = appWidgetManager.installedProviders.first {
                         it.provider == target
                     }
                     val snapshot = preview.compose(
@@ -228,10 +222,9 @@
     /** Check which receivers still exist, and clean the data store to only keep those. */
     internal suspend fun cleanReceivers() {
         val packageName = context.packageName
-        @Suppress("ListIterator")
         val receivers = appWidgetManager.installedProviders
-            .fastFilter { it.provider.packageName == packageName }
-            .fastMap { it.provider.className }
+            .filter { it.provider.packageName == packageName }
+            .map { it.provider.className }
             .toSet()
         dataStore.updateData { prefs ->
             val knownReceivers = prefs[providersKey] ?: return@updateData prefs
@@ -239,7 +232,7 @@
             if (toRemove.isEmpty()) return@updateData prefs
             prefs.toMutablePreferences().apply {
                 this[providersKey] = knownReceivers - toRemove
-                toRemove.fastForEach { receiver -> remove(providerKey(receiver)) }
+                toRemove.forEach { receiver -> remove(providerKey(receiver)) }
             }.toPreferences()
         }
     }
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/IgnoreResult.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/IgnoreResult.kt
index d6711c1..59062de 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/IgnoreResult.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/IgnoreResult.kt
@@ -17,7 +17,6 @@
 package androidx.glance.appwidget
 
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.util.fastAny
 import androidx.glance.Emittable
 import androidx.glance.EmittableWithChildren
 import androidx.glance.GlanceComposable
@@ -52,7 +51,7 @@
     if (this is EmittableIgnoreResult) {
         return true
     } else if (this is EmittableWithChildren) {
-        if (children.fastAny { it.shouldIgnoreResult() }) return true
+        if (children.any { it.shouldIgnoreResult() }) return true
     }
     return false
 }
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/LayoutSelection.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/LayoutSelection.kt
index b9bb899..e4ce545 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/LayoutSelection.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/LayoutSelection.kt
@@ -26,7 +26,6 @@
 import androidx.annotation.LayoutRes
 import androidx.annotation.RequiresApi
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastForEach
 import androidx.glance.GlanceModifier
 import androidx.glance.findModifier
 import androidx.glance.layout.Alignment
@@ -363,7 +362,7 @@
         ?: throw IllegalStateException("No child for position $pos and size $width x $height")
     children.values
         .filter { it != stubId }
-        .fastForEach {
+        .forEach {
             inflateViewStub(
                 translationContext, it, R.layout.glance_deleted_view, R.id.deletedViewId)
         }
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/LinearProgressIndicator.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/LinearProgressIndicator.kt
index e314c1a..a300ca5 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/LinearProgressIndicator.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/LinearProgressIndicator.kt
@@ -16,6 +16,7 @@
 
 package androidx.glance.appwidget
 
+import androidx.annotation.RestrictTo
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.graphics.Color
 import androidx.glance.Emittable
@@ -76,7 +77,8 @@
     )
 }
 
-internal class EmittableLinearProgressIndicator : Emittable {
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class EmittableLinearProgressIndicator : Emittable {
     override var modifier: GlanceModifier = GlanceModifier
     var progress: Float = 0.0f
     var indeterminate: Boolean = false
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt
index ec3d23d..7a1f57e 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt
@@ -18,11 +18,6 @@
 import android.os.Build
 import android.util.Log
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastAll
-import androidx.compose.ui.util.fastAny
-import androidx.compose.ui.util.fastFold
-import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastForEachIndexed
 import androidx.glance.BackgroundModifier
 import androidx.glance.Emittable
 import androidx.glance.EmittableButton
@@ -33,6 +28,9 @@
 import androidx.glance.ImageProvider
 import androidx.glance.action.ActionModifier
 import androidx.glance.action.LambdaAction
+import androidx.glance.action.NoRippleOverride
+import androidx.glance.addChild
+import androidx.glance.addChildIfNotNull
 import androidx.glance.appwidget.action.CompoundButtonAction
 import androidx.glance.extractModifier
 import androidx.glance.findModifier
@@ -68,8 +66,7 @@
  * [EmittableBox].
  */
 private fun coerceToOneChild(container: EmittableWithChildren) {
-    if (container.children.isNotEmpty() && container.children.fastAll { it is EmittableSizeBox }) {
-        @Suppress("ListIterator")
+    if (container.children.isNotEmpty() && container.children.all { it is EmittableSizeBox }) {
         for (item in container.children) {
             item as EmittableSizeBox
             if (item.children.size == 1) continue
@@ -95,20 +92,20 @@
  * fillMaxSize. Otherwise, the behavior depends on the version of Android.
  */
 private fun EmittableWithChildren.normalizeSizes() {
-    children.fastForEach { child ->
+    children.forEach { child ->
         if (child is EmittableWithChildren) {
             child.normalizeSizes()
         }
     }
     if ((modifier.findModifier<HeightModifier>()?.height ?: Dimension.Wrap) is Dimension.Wrap &&
-        children.fastAny { child ->
+        children.any { child ->
             child.modifier.findModifier<HeightModifier>()?.height is Dimension.Fill
         }
     ) {
         modifier = modifier.fillMaxHeight()
     }
     if ((modifier.findModifier<WidthModifier>()?.width ?: Dimension.Wrap) is Dimension.Wrap &&
-        children.fastAny { child ->
+        children.any { child ->
             child.modifier.findModifier<WidthModifier>()?.width is Dimension.Fill
         }
     ) {
@@ -118,7 +115,7 @@
 
 /** Transform each node in the tree. */
 private fun EmittableWithChildren.transformTree(block: (Emittable) -> Emittable) {
-    children.fastForEachIndexed { index, child ->
+    children.forEachIndexed { index, child ->
         val newChild = block(child)
         children[index] = newChild
         if (newChild is EmittableWithChildren) newChild.transformTree(block)
@@ -139,7 +136,6 @@
  * will be updated for the composition in all sizes. This is why there can be multiple LambdaActions
  * for each key, even after de-duping.
  */
-@Suppress("ListIterator")
 internal fun EmittableWithChildren.updateLambdaActionKeys(): Map<String, List<LambdaAction>> =
     children.foldIndexed(
         mutableMapOf<String, MutableList<LambdaAction>>()
@@ -148,7 +144,8 @@
             child.modifier.extractLambdaAction()
         if (action != null &&
             child !is EmittableSizeBox &&
-            child !is EmittableLazyItemWithChildren) {
+            child !is EmittableLazyItemWithChildren
+        ) {
             val newKey = action.key + "+$index"
             val newAction = LambdaAction(newKey, action.block)
             actions.getOrPut(newKey) { mutableListOf() }.add(newAction)
@@ -169,6 +166,7 @@
             action is LambdaAction -> action to modifiers
             action is CompoundButtonAction && action.innerAction is LambdaAction ->
                 action.innerAction to modifiers
+
             else -> null to modifiers
         }
     }
@@ -205,11 +203,11 @@
         // before the target in the wrapper box. This allows us to support content scale as well as
         // can help support additional processing on background images. Note: Button's don't support
         // bg image modifier.
-        (it is BackgroundModifier && it.imageProvider != null) ||
-        // R- buttons are implemented using box, images and text.
-        (isButton && Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) ||
-         // Ripples are implemented by placing a drawable after the target in the wrapper box.
-        (it is ActionModifier && !hasBuiltinRipple())
+        (it is BackgroundModifier.Image) ||
+            // R- buttons are implemented using box, images and text.
+            (isButton && Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) ||
+            // Ripples are implemented by placing a drawable after the target in the wrapper box.
+            (it is ActionModifier && !hasBuiltinRipple())
     }
     if (!shouldWrapTargetInABox) return target
 
@@ -234,7 +232,7 @@
                 // drawable's base was white/none, applying transparent tint will lead to black
                 // color. This shouldn't be issue for icon type drawables, but in this case we are
                 // emulating colored outline. So, we apply tint as well as alpha.
-                bgModifier.colorProvider?.let {
+                (bgModifier as? BackgroundModifier.Color)?.colorProvider?.let {
                     colorFilterParams = TintAndAlphaColorFilterParams(it)
                 }
                 contentScale = ContentScale.FillBounds
@@ -244,14 +242,19 @@
             // is applied back to the target. Note: We could have hoisted the bg color to box
             // instead of adding it back to the target, but for buttons, we also add an outline
             // background to the box.
-            if (bgModifier.imageProvider != null) {
-                backgroundImage = EmittableImage().apply {
-                    modifier = GlanceModifier.fillMaxSize()
-                    provider = bgModifier.imageProvider
-                    contentScale = bgModifier.contentScale
+            when (bgModifier) {
+                is BackgroundModifier.Image -> {
+                    backgroundImage = EmittableImage().apply {
+                        modifier = GlanceModifier.fillMaxSize()
+                        provider = bgModifier.imageProvider
+                        contentScale = bgModifier.contentScale
+                        colorFilterParams = bgModifier.colorFilter?.colorFilterParams
+                    }
                 }
-            } else { // is a background color modifier
-                targetModifiers += bgModifier
+
+                is BackgroundModifier.Color -> {
+                    targetModifiers += bgModifier
+                }
             }
         }
     }
@@ -263,9 +266,15 @@
         targetModifiersMinusBg.extractModifier<ActionModifier>()
     boxModifiers += actionModifier
     if (actionModifier != null && !hasBuiltinRipple()) {
+        val maybeRippleOverride = actionModifier.rippleOverride
         val rippleImageProvider =
-            if (isButton) ImageProvider(R.drawable.glance_button_ripple)
-            else ImageProvider(R.drawable.glance_ripple)
+            if (maybeRippleOverride != NoRippleOverride) {
+                ImageProvider(maybeRippleOverride)
+            } else if (isButton) {
+                ImageProvider(R.drawable.glance_button_ripple)
+            } else {
+                ImageProvider(R.drawable.glance_ripple)
+            }
         rippleImage = EmittableImage().apply {
             modifier = GlanceModifier.fillMaxSize()
             provider = rippleImageProvider
@@ -289,22 +298,24 @@
 
     return EmittableBox().apply {
         modifier = boxModifiers.collect()
+        target.modifier = targetModifiers.collect()
+
         if (isButton) contentAlignment = Alignment.Center
 
-        backgroundImage?.let { children += it }
-        children += target.apply { modifier = targetModifiers.collect() }
-        rippleImage?.let { children += it }
+        addChildIfNotNull(backgroundImage)
+        addChild(target)
+        addChildIfNotNull(rippleImage)
     }
 }
 
 private fun Emittable.hasBuiltinRipple() =
     this is EmittableSwitch ||
-    this is EmittableRadioButton ||
-    this is EmittableCheckBox ||
-     // S+ versions use a native button with fixed rounded corners and matching ripple set in
-     // layout xml. In R- versions, buttons are implemented using a background drawable with
-     // rounded corners and an EmittableText in R- versions.
-    (this is EmittableButton && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
+        this is EmittableRadioButton ||
+        this is EmittableCheckBox ||
+        // S+ versions use a native button with fixed rounded corners and matching ripple set in
+        // layout xml. In R- versions, buttons are implemented using a background drawable with
+        // rounded corners and an EmittableText in R- versions.
+        (this is EmittableButton && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
 
 private data class ExtractedSizeModifiers(
     val sizeModifiers: GlanceModifier = GlanceModifier,
@@ -320,7 +331,8 @@
         foldIn(ExtractedSizeModifiers()) { acc, modifier ->
             if (modifier is WidthModifier ||
                 modifier is HeightModifier ||
-                modifier is CornerRadiusModifier) {
+                modifier is CornerRadiusModifier
+            ) {
                 acc.copy(sizeModifiers = acc.sizeModifiers.then(modifier))
             } else {
                 acc.copy(nonSizeModifiers = acc.nonSizeModifiers.then(modifier))
@@ -344,6 +356,6 @@
 }
 
 private fun MutableList<GlanceModifier?>.collect(): GlanceModifier =
-    fastFold(GlanceModifier) { acc: GlanceModifier, mod: GlanceModifier? ->
+    fold(GlanceModifier) { acc: GlanceModifier, mod: GlanceModifier? ->
         mod?.let { acc.then(mod) } ?: acc
     }
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RemoteCollectionItems.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RemoteCollectionItems.kt
index 8fff92a..61c8bcb 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RemoteCollectionItems.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RemoteCollectionItems.kt
@@ -18,7 +18,6 @@
 
 import android.annotation.SuppressLint
 import android.widget.RemoteViews
-import androidx.compose.ui.util.fastMap
 
 /** Representation of a fixed list of items to be displayed in a RemoteViews collection.  */
 internal class RemoteCollectionItems private constructor(
@@ -32,7 +31,6 @@
             "RemoteCollectionItems has different number of ids and views"
         }
         require(_viewTypeCount >= 1) { "View type count must be >= 1" }
-        @Suppress("ListIterator")
         val layoutIdCount = views.map { it.layoutId }.distinct().count()
         require(layoutIdCount <= _viewTypeCount) {
             "View type count is set to $_viewTypeCount, but the collection contains " +
@@ -133,8 +131,7 @@
             if (viewTypeCount < 1) {
                 // If a view type count wasn't specified, set it to be the number of distinct
                 // layout ids used in the items.
-                @Suppress("ListIterator")
-                viewTypeCount = views.fastMap { it.layoutId }.distinct().count()
+                viewTypeCount = views.map { it.layoutId }.distinct().count()
             }
             return RemoteCollectionItems(
                 ids.toLongArray(),
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RemoteViewsRoot.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RemoteViewsRoot.kt
index 7ca531c..df0e178 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RemoteViewsRoot.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RemoteViewsRoot.kt
@@ -18,7 +18,6 @@
 
 import androidx.annotation.RestrictTo
 import androidx.annotation.RestrictTo.Scope
-import androidx.compose.ui.util.fastMap
 import androidx.glance.Emittable
 import androidx.glance.EmittableWithChildren
 import androidx.glance.GlanceModifier
@@ -32,7 +31,7 @@
     override var modifier: GlanceModifier = GlanceModifier
     override fun copy(): Emittable = RemoteViewsRoot(maxDepth).also {
         it.modifier = modifier
-        it.children.addAll(children.fastMap { it.copy() })
+        it.children.addAll(children.map { it.copy() })
     }
 
     override fun toString(): String = "RemoteViewsRoot(" +
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt
index edb9d83..d94e52f 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt
@@ -31,10 +31,6 @@
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.isSpecified
-import androidx.compose.ui.util.fastAll
-import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastForEachIndexed
-import androidx.compose.ui.util.fastMap
 import androidx.core.widget.RemoteViewsCompat.setLinearLayoutGravity
 import androidx.glance.Emittable
 import androidx.glance.EmittableButton
@@ -109,13 +105,13 @@
     children: List<Emittable>,
     rootViewIndex: Int
 ): RemoteViews {
-    if (children.fastAll { it is EmittableSizeBox }) {
+    if (children.all { it is EmittableSizeBox }) {
         // If the children of root are all EmittableSizeBoxes, then we must translate each
         // EmittableSizeBox into a distinct RemoteViews object. Then, we combine them into one
         // multi-sized RemoteViews (a RemoteViews that contains either landscape & portrait RVs or
         // multiple RVs mapped by size).
         val sizeMode = (children.first() as EmittableSizeBox).sizeMode
-        val views = children.fastMap { child ->
+        val views = children.map { child ->
             val size = (child as EmittableSizeBox).size
             val remoteViewsInfo = createRootView(translationContext, child.modifier, rootViewIndex)
             val rv = remoteViewsInfo.remoteViews.apply {
@@ -130,11 +126,10 @@
             is SizeMode.Single -> views.single().second
             is SizeMode.Responsive, SizeMode.Exact -> {
                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-                    @Suppress("ListIterator")
                     Api31Impl.createRemoteViews(views.toMap())
                 } else {
                     require(views.size == 1 || views.size == 2) { "unsupported views size" }
-                    combineLandscapeAndPortrait(views.fastMap { it.second })
+                    combineLandscapeAndPortrait(views.map { it.second })
                 }
             }
         }
@@ -312,7 +307,7 @@
         element.modifier,
         viewDef
     )
-    element.children.fastForEach {
+    element.children.forEach {
         it.modifier = it.modifier.then(AlignmentModifier(element.contentAlignment))
     }
     setChildren(
@@ -397,7 +392,6 @@
 }
 
 private fun checkSelectableGroupChildren(children: List<Emittable>) {
-    @Suppress("ListIterator")
     check(children.count { it is EmittableRadioButton && it.checked } <= 1) {
         "When using GlanceModifier.selectableGroup(), no more than one RadioButton " +
         "may be checked at a time."
@@ -416,7 +410,7 @@
         }
         element.remoteViews.copy().apply {
             removeAllViews(element.containerViewId)
-            element.children.fastForEachIndexed { index, child ->
+            element.children.forEachIndexed { index, child ->
                 val rvInfo = createRootView(translationContext, child.modifier, index)
                 val rv = rvInfo.remoteViews
                 rv.translateChild(translationContext.forRoot(rvInfo), child)
@@ -472,8 +466,7 @@
     parentDef: InsertedViewInfo,
     children: List<Emittable>
 ) {
-    @Suppress("ListIterator")
-    children.take(10).fastForEachIndexed { index, child ->
+    children.take(10).forEachIndexed { index, child ->
         translateChild(
             translationContext.forChild(parent = parentDef, pos = index),
             child,
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/SizeBox.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/SizeBox.kt
index e1727c0..4c41537 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/SizeBox.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/SizeBox.kt
@@ -20,7 +20,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.unit.DpSize
-import androidx.compose.ui.util.fastMap
 import androidx.glance.Emittable
 import androidx.glance.EmittableWithChildren
 import androidx.glance.GlanceModifier
@@ -49,7 +48,7 @@
     override fun copy(): Emittable = EmittableSizeBox().also {
         it.size = size
         it.sizeMode = sizeMode
-        it.children.addAll(children.fastMap { it.copy() })
+        it.children.addAll(children.map { it.copy() })
     }
 
     override fun toString(): String = "EmittableSizeBox(" +
@@ -107,11 +106,11 @@
         } else {
             val smallestSize = sizeMode.sizes.sortedBySize()[0]
             LocalAppWidgetOptions.current.extractOrientationSizes()
-                .fastMap { findBestSize(it, sizeMode.sizes) ?: smallestSize }
+                .map { findBestSize(it, sizeMode.sizes) ?: smallestSize }
                 .ifEmpty { listOf(smallestSize, smallestSize) }
         }
     }
-    sizes.distinct().fastMap { size ->
+    sizes.distinct().map { size ->
         SizeBox(size, sizeMode, content)
     }
 }
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/WidgetLayout.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/WidgetLayout.kt
index bae572a..49e0d30 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/WidgetLayout.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/WidgetLayout.kt
@@ -22,7 +22,6 @@
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.annotation.VisibleForTesting
-import androidx.compose.ui.util.fastMap
 import androidx.datastore.core.CorruptionException
 import androidx.datastore.core.DataStore
 import androidx.datastore.core.DataStoreFactory
@@ -115,7 +114,6 @@
                 )
                 LayoutProto.LayoutConfig.getDefaultInstance()
             }
-            @Suppress("ListIterator")
             val layouts = config.layoutList.associate {
                 it.layout to it.layoutIndex
             }.toMutableMap()
@@ -243,7 +241,7 @@
             is EmittableLazyColumn -> setLazyListColumn(element)
         }
         if (element is EmittableWithChildren && element !is EmittableLazyList) {
-            addAllChildren(element.children.fastMap { createNode(context, it) })
+            addAllChildren(element.children.map { createNode(context, it) })
         }
     }.build()
 
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/component/Buttons.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/component/Buttons.kt
new file mode 100644
index 0000000..e2c6559
--- /dev/null
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/component/Buttons.kt
@@ -0,0 +1,467 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.glance.appwidget.component
+
+import android.os.Build
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.glance.Button
+import androidx.glance.ButtonColors
+import androidx.glance.ButtonDefaults
+import androidx.glance.ColorFilter
+import androidx.glance.GlanceModifier
+import androidx.glance.GlanceTheme
+import androidx.glance.Image
+import androidx.glance.ImageProvider
+import androidx.glance.action.Action
+import androidx.glance.action.NoRippleOverride
+import androidx.glance.action.action
+import androidx.glance.action.clickable
+import androidx.glance.appwidget.R
+import androidx.glance.appwidget.cornerRadius
+import androidx.glance.appwidget.enabled
+import androidx.glance.background
+import androidx.glance.layout.Alignment
+import androidx.glance.layout.Box
+import androidx.glance.layout.Row
+import androidx.glance.layout.Spacer
+import androidx.glance.layout.padding
+import androidx.glance.layout.size
+import androidx.glance.layout.width
+import androidx.glance.text.FontWeight
+import androidx.glance.text.Text
+import androidx.glance.text.TextStyle
+import androidx.glance.unit.ColorProvider
+
+/**
+ * A button styled per Material3. It has a filled background. It is more opinionated than [Button]
+ * and suitable for uses where M3 is preferred.
+ *
+ * @param text The text that this button will show.
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param icon An optional leading icon placed before the text.
+ * @param colors The colors to use for the background and content of the button.
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated.
+ * @param key A stable and unique key that identifies the action for this button. This ensures
+ * that the correct action is triggered, especially in cases of items that change order. If not
+ * provided we use the key that is automatically generated by the Compose runtime, which is unique
+ * for every exact code location in the composition tree.
+ */
+@Composable
+fun FilledButton(
+    text: String,
+    onClick: () -> Unit,
+    modifier: GlanceModifier = GlanceModifier,
+    enabled: Boolean = true,
+    icon: ImageProvider? = null,
+    colors: ButtonColors = ButtonDefaults.buttonColors(),
+    maxLines: Int = Int.MAX_VALUE,
+    key: String? = null
+) = FilledButton(
+    text = text,
+    onClick = action(block = onClick, key = key),
+    modifier = modifier,
+    enabled = enabled,
+    icon = icon,
+    colors = colors,
+    maxLines = maxLines,
+)
+
+/**
+ * A button styled per Material3. It has a filled background. It is more opinionated than [Button]
+ * and suitable for uses where M3 is preferred.
+ *
+ * @param text The text that this button will show.
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param icon An optional leading icon placed before the text.
+ * @param colors The colors to use for the background and content of the button.
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated.
+ */
+@Composable
+fun FilledButton(
+    text: String,
+    onClick: Action,
+    modifier: GlanceModifier = GlanceModifier,
+    enabled: Boolean = true,
+    icon: ImageProvider? = null,
+    colors: ButtonColors = ButtonDefaults.buttonColors(),
+    maxLines: Int = Int.MAX_VALUE,
+) = M3TextButton(
+    text = text,
+    modifier = modifier,
+    enabled = enabled,
+    icon = icon,
+    contentColor = colors.contentColor,
+    backgroundTint = colors.backgroundColor,
+    backgroundResource = R.drawable.glance_component_btn_filled,
+    onClick = onClick,
+    maxLines = maxLines,
+)
+
+/**
+ * An outline button styled per Material3. It has a transparent background. It is more opinionated
+ * than [Button] and suitable for uses where M3 is preferred.
+ *
+ * @param text The text that this button will show.
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param icon An optional leading icon placed before the text.
+ * @param contentColor The color used for the text, optional icon tint, and outline.
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated.
+ * @param key A stable and unique key that identifies the action for this button. This ensures
+ * that the correct action is triggered, especially in cases of items that change order. If not
+ * provided we use the key that is automatically generated by the Compose runtime, which is unique
+ * for every exact code location in the composition tree.
+ */
+@Composable
+fun OutlineButton(
+    text: String,
+    contentColor: ColorProvider,
+    onClick: () -> Unit,
+    modifier: GlanceModifier = GlanceModifier,
+    enabled: Boolean = true,
+    icon: ImageProvider? = null,
+    maxLines: Int = Int.MAX_VALUE,
+    key: String? = null
+) = OutlineButton(
+    text = text,
+    contentColor = contentColor,
+    onClick = action(block = onClick, key = key),
+    modifier = modifier,
+    enabled = enabled,
+    icon = icon,
+    maxLines = maxLines,
+)
+
+/**
+ * An outline button styled per Material3. It has a transparent background. It is more opinionated
+ * than [Button] and suitable for uses where M3 is preferred.
+ *
+ * @param text The text that this button will show.
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param icon An optional leading icon placed before the text.
+ * @param contentColor The color used for the text, optional icon tint, and outline.
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated.
+ */
+@Composable
+fun OutlineButton(
+    text: String,
+    contentColor: ColorProvider,
+    onClick: Action,
+    modifier: GlanceModifier = GlanceModifier,
+    enabled: Boolean = true,
+    icon: ImageProvider? = null,
+    maxLines: Int = Int.MAX_VALUE,
+) {
+    val bg: ColorProvider = contentColor
+    val fg: ColorProvider = contentColor
+
+    M3TextButton(
+        text = text,
+        onClick = onClick,
+        modifier = modifier,
+        enabled = enabled,
+        icon = icon,
+        contentColor = fg,
+        backgroundResource = R.drawable.glance_component_btn_outline,
+        backgroundTint = bg,
+        maxLines = maxLines,
+    )
+}
+
+/**
+ * Intended to fill the role of primary icon button or fab.
+ *
+ * @param imageProvider the icon to be drawn in the button
+ * @param contentDescription Text used by accessibility services to describe what this image
+ * represents. This text should be localized, such as by using
+ * androidx.compose.ui.res.stringResource or similar
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param backgroundColor The color to tint the button's background.
+ * @param contentColor The color to tint the button's icon.
+ * @param key A stable and unique key that identifies the action for this button. This ensures
+ * that the correct action is triggered, especially in cases of items that change order. If not
+ * provided we use the key that is automatically generated by the Compose runtime, which is unique
+ * for every exact code location in the composition tree.
+ */
+@Composable
+fun SquareIconButton(
+    imageProvider: ImageProvider,
+    contentDescription: String?,
+    onClick: () -> Unit,
+    modifier: GlanceModifier = GlanceModifier,
+    enabled: Boolean = true,
+    backgroundColor: ColorProvider = GlanceTheme.colors.primary,
+    contentColor: ColorProvider = GlanceTheme.colors.onPrimary,
+    key: String? = null
+) = SquareIconButton(
+    imageProvider = imageProvider,
+    contentDescription = contentDescription,
+    onClick = action(block = onClick, key = key),
+    modifier = modifier,
+    enabled = enabled,
+    backgroundColor = backgroundColor,
+    contentColor = contentColor,
+)
+
+/**
+ * Intended to fill the role of primary icon button or fab.
+ *
+ * @param imageProvider the icon to be drawn in the button
+ * @param contentDescription Text used by accessibility services to describe what this image
+ * represents. This text should be localized, such as by using
+ * androidx.compose.ui.res.stringResource or similar
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param backgroundColor The color to tint the button's background.
+ * @param contentColor The color to tint the button's icon.
+ */
+@Composable
+fun SquareIconButton(
+    imageProvider: ImageProvider,
+    contentDescription: String?,
+    onClick: Action,
+    modifier: GlanceModifier = GlanceModifier,
+    enabled: Boolean = true,
+    backgroundColor: ColorProvider = GlanceTheme.colors.primary,
+    contentColor: ColorProvider = GlanceTheme.colors.onPrimary,
+) = M3IconButton(
+    imageProvider = imageProvider,
+    contentDescription = contentDescription,
+    backgroundColor = backgroundColor,
+    contentColor = contentColor,
+    shape = IconButtonShape.Square,
+    modifier = modifier,
+    enabled = enabled,
+    onClick = onClick,
+)
+
+/**
+ * Intended to fill the role of secondary icon button.
+ * Background color may be null to have the button display as an icon with a 48x48dp hit area.
+ *
+ * @param imageProvider the icon to be drawn in the button
+ * @param contentDescription Text used by accessibility services to describe what this image
+ * represents. This text should be localized, such as by using
+ * androidx.compose.ui.res.stringResource or similar
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param backgroundColor The color to tint the button's background. May be null to make background
+ * transparent.
+ * @param contentColor The color to tint the button's icon.
+ * @param key A stable and unique key that identifies the action for this button. This ensures
+ * that the correct action is triggered, especially in cases of items that change order. If not
+ * provided we use the key that is automatically generated by the Compose runtime, which is unique
+ * for every exact code location in the composition tree.
+ */
+@Composable
+fun CircleIconButton(
+    imageProvider: ImageProvider,
+    contentDescription: String?,
+    onClick: () -> Unit,
+    modifier: GlanceModifier = GlanceModifier,
+    enabled: Boolean = true,
+    backgroundColor: ColorProvider? = GlanceTheme.colors.background,
+    contentColor: ColorProvider = GlanceTheme.colors.onSurface,
+    key: String? = null
+) = CircleIconButton(
+    imageProvider = imageProvider,
+    contentDescription = contentDescription,
+    backgroundColor = backgroundColor,
+    contentColor = contentColor,
+    modifier = modifier,
+    enabled = enabled,
+    onClick = action(block = onClick, key = key)
+)
+
+/**
+ * Intended to fill the role of secondary icon button.
+ * Background color may be null to have the button display as an icon with a 48x48dp hit area.
+ *
+ * @param imageProvider the icon to be drawn in the button
+ * @param contentDescription Text used by accessibility services to describe what this image
+ * represents. This text should be localized, such as by using
+ * androidx.compose.ui.res.stringResource or similar
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param backgroundColor The color to tint the button's background. May be null to make background
+ * transparent.
+ * @param contentColor The color to tint the button's icon.
+ */
+@Composable
+fun CircleIconButton(
+    imageProvider: ImageProvider,
+    contentDescription: String?,
+    onClick: Action,
+    modifier: GlanceModifier = GlanceModifier,
+    enabled: Boolean = true,
+    backgroundColor: ColorProvider? = GlanceTheme.colors.background,
+    contentColor: ColorProvider = GlanceTheme.colors.onSurface,
+) = M3IconButton(
+    imageProvider = imageProvider,
+    contentDescription = contentDescription,
+    backgroundColor = backgroundColor,
+    contentColor = contentColor,
+    shape = IconButtonShape.Circle,
+    modifier = modifier,
+    enabled = enabled,
+    onClick = onClick,
+)
+
+private enum class IconButtonShape(
+    @DrawableRes val shape: Int,
+    @DimenRes val cornerRadius: Int,
+    @DrawableRes val ripple: Int,
+    val defaultSize: Dp
+) {
+    Square(
+        R.drawable.glance_component_btn_square,
+        R.dimen.glance_component_square_icon_button_corners,
+        ripple = if (isAtLeastApi31) NoRippleOverride
+            else R.drawable.glance_component_square_button_ripple,
+        defaultSize = 60.dp
+    ),
+    Circle(
+        R.drawable.glance_component_btn_circle,
+        R.dimen.glance_component_circle_icon_button_corners,
+        ripple = if (isAtLeastApi31) NoRippleOverride
+            else R.drawable.glance_component_circle_button_ripple,
+        defaultSize = 48.dp
+    )
+}
+
+@Composable
+private fun M3IconButton(
+    imageProvider: ImageProvider,
+    contentDescription: String?,
+    contentColor: ColorProvider,
+    backgroundColor: ColorProvider?,
+    shape: IconButtonShape,
+    onClick: Action,
+    modifier: GlanceModifier,
+    enabled: Boolean,
+) {
+
+    val backgroundModifier = if (backgroundColor == null)
+        GlanceModifier
+    else GlanceModifier.background(
+        ImageProvider(shape.shape),
+        tint = ColorFilter.tint(backgroundColor)
+    )
+
+    Box(
+        contentAlignment = Alignment.Center,
+        modifier = GlanceModifier
+            .size(shape.defaultSize) // acts as a default if not overridden by [modifier]
+            .then(modifier)
+            .then(backgroundModifier)
+            .clickable(onClick = onClick, rippleOverride = shape.ripple)
+            .enabled(enabled)
+            .then(maybeRoundCorners(shape.cornerRadius))
+    ) {
+        Image(
+            provider = imageProvider,
+            contentDescription = contentDescription,
+            colorFilter = ColorFilter.tint(contentColor),
+            modifier = GlanceModifier.size(24.dp)
+        )
+    }
+}
+
+@Composable
+private fun M3TextButton(
+    text: String,
+    onClick: Action,
+    modifier: GlanceModifier,
+    enabled: Boolean = true,
+    icon: ImageProvider?,
+    contentColor: ColorProvider,
+    @DrawableRes backgroundResource: Int,
+    backgroundTint: ColorProvider,
+    maxLines: Int,
+) {
+    val iconSize = 18.dp
+    val totalHorizontalPadding = if (icon != null) 24.dp else 16.dp
+
+    val Text = @Composable {
+        Text(
+            text = text,
+            style = TextStyle(color = contentColor, fontSize = 14.sp, FontWeight.Medium),
+            maxLines = maxLines
+        )
+    }
+
+    Box(
+        modifier = modifier
+            .padding(start = 16.dp, end = totalHorizontalPadding, top = 10.dp, bottom = 10.dp)
+            .background(ImageProvider(backgroundResource), tint = ColorFilter.tint(backgroundTint))
+            .enabled(enabled)
+            .clickable(
+                onClick = onClick,
+                rippleOverride = if (isAtLeastApi31) NoRippleOverride
+                else R.drawable.glance_component_m3_button_ripple
+            )
+            .then(maybeRoundCorners(R.dimen.glance_component_button_corners)),
+        contentAlignment = Alignment.Center
+    ) {
+
+        if (icon != null) {
+            Row(verticalAlignment = Alignment.Vertical.CenterVertically) {
+                Image(
+                    provider = icon,
+                    contentDescription = null,
+                    colorFilter = ColorFilter.tint(contentColor),
+                    modifier = GlanceModifier.size(iconSize)
+                ) // TODO: do we need a content description for a button icon?
+                Spacer(GlanceModifier.width(8.dp))
+                Text()
+            }
+        } else {
+            Box(GlanceModifier.size(iconSize)) {
+                // for accessibility only: force button to be the same min height as the icon
+                // version.
+                // remove once b/290677181 is addressed
+            }
+            Text()
+        }
+    }
+}
+
+private val isAtLeastApi31 get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
+private fun maybeRoundCorners(@DimenRes radius: Int) =
+    if (isAtLeastApi31)
+        GlanceModifier.cornerRadius(radius)
+    else GlanceModifier
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyList.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyList.kt
index 17e4528..2430387 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyList.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyList.kt
@@ -19,8 +19,6 @@
 import android.os.Bundle
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.key
-import androidx.compose.ui.util.fastForEachIndexed
-import androidx.compose.ui.util.fastMap
 import androidx.glance.Emittable
 import androidx.glance.EmittableLazyItemWithChildren
 import androidx.glance.EmittableWithChildren
@@ -123,7 +121,7 @@
     }
     listScopeImpl.apply(content)
     return {
-        itemList.fastForEachIndexed { index, (itemId, composable) ->
+        itemList.forEachIndexed { index, (itemId, composable) ->
             val id = itemId.takeIf { it != LazyListScope.UnspecifiedItemId }
                 ?: (ReservedItemIdRangeEnd - index)
             check(id != LazyListScope.UnspecifiedItemId) { "Implicit list item ids exhausted." }
@@ -299,7 +297,7 @@
     override fun copy(): Emittable = EmittableLazyListItem().also {
         it.itemId = itemId
         it.alignment = alignment
-        it.children.addAll(children.fastMap { it.copy() })
+        it.children.addAll(children.map { it.copy() })
     }
 
     override fun toString() =
@@ -312,6 +310,6 @@
         it.modifier = modifier
         it.horizontalAlignment = horizontalAlignment
         it.activityOptions = activityOptions
-        it.children.addAll(children.fastMap { it.copy() })
+        it.children.addAll(children.map { it.copy() })
     }
 }
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt
index 72b8eb4..53a41ce 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt
@@ -20,8 +20,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.key
 import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.util.fastForEachIndexed
-import androidx.compose.ui.util.fastMap
 import androidx.glance.Emittable
 import androidx.glance.EmittableLazyItemWithChildren
 import androidx.glance.EmittableWithChildren
@@ -130,7 +128,7 @@
     }
     listScopeImpl.apply(content)
     return {
-        itemList.fastForEachIndexed { index, (itemId, composable) ->
+        itemList.forEachIndexed { index, (itemId, composable) ->
             val id = itemId.takeIf { it != LazyVerticalGridScope.UnspecifiedItemId }
                 ?: (ReservedItemIdRangeEnd - index)
             check(id != LazyVerticalGridScope.UnspecifiedItemId) {
@@ -300,7 +298,7 @@
     override fun copy(): Emittable = EmittableLazyVerticalGridListItem().also {
         it.itemId = itemId
         it.alignment = alignment
-        it.children.addAll(children.fastMap { it.copy() })
+        it.children.addAll(children.map { it.copy() })
     }
 
     override fun toString(): String =
@@ -316,7 +314,7 @@
         it.horizontalAlignment = horizontalAlignment
         it.gridCells = gridCells
         it.activityOptions = activityOptions
-        it.children.addAll(children.fastMap { it.copy() })
+        it.children.addAll(children.map { it.copy() })
     }
 }
 
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/LazyListTranslator.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/LazyListTranslator.kt
index eb1c6fe..4810dd6 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/LazyListTranslator.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/LazyListTranslator.kt
@@ -75,7 +75,6 @@
     )
     val items = RemoteCollectionItems.Builder().apply {
         val childContext = translationContext.forLazyCollection(viewDef.mainViewId)
-        @Suppress("ListIterator")
         element.children.foldIndexed(false) { position, previous, itemEmittable ->
             itemEmittable as EmittableLazyListItem
             val itemId = itemEmittable.itemId
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/LazyVerticalGridTranslator.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/LazyVerticalGridTranslator.kt
index d9b74d0..eda245b 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/LazyVerticalGridTranslator.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/LazyVerticalGridTranslator.kt
@@ -88,7 +88,6 @@
     )
     val items = RemoteCollectionItems.Builder().apply {
         val childContext = translationContext.forLazyCollection(viewDef.mainViewId)
-        @Suppress("ListIterator")
         element.children.foldIndexed(false) { position, previous, itemEmittable ->
             itemEmittable as EmittableLazyVerticalGridListItem
             val itemId = itemEmittable.itemId
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/TextTranslator.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/TextTranslator.kt
index 806a7cc..6a219a7 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/TextTranslator.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/TextTranslator.kt
@@ -35,7 +35,6 @@
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.util.fastForEach
 import androidx.core.widget.RemoteViewsCompat.setTextViewGravity
 import androidx.core.widget.RemoteViewsCompat.setTextViewMaxLines
 import androidx.core.widget.RemoteViewsCompat.setTextViewTextColor
@@ -131,7 +130,7 @@
             spans.add(AlignmentSpan.Standard(align.toAlignment(translationContext.isRtl)))
         }
     }
-    spans.fastForEach { span ->
+    spans.forEach { span ->
         content.setSpan(span, 0, length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
     }
     setTextViewText(resId, content)
diff --git a/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_circle.xml b/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_circle.xml
new file mode 100644
index 0000000..efdd739
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_circle.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <size
+        android:width="48dp"
+        android:height="48dp" />
+    <solid android:color="@android:color/black" />
+</shape>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_filled.xml b/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_filled.xml
new file mode 100644
index 0000000..d267cc2
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_filled.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@android:color/black" />
+    <corners android:radius="@dimen/glance_component_button_corners"/>
+</shape>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_outline.xml b/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_outline.xml
new file mode 100644
index 0000000..e8b905f
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_outline.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="@dimen/glance_component_button_corners"/>
+    <stroke android:width="1dp" android:color="@android:color/black" />
+    <solid android:color="@android:color/transparent" />
+</shape>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_square.xml b/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_square.xml
new file mode 100644
index 0000000..df423d0
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_square.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="@dimen/glance_component_square_icon_button_corners" />
+    <solid android:color="@android:color/black" />
+</shape>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/main/res/drawable/glance_component_circle_button_ripple.xml b/glance/glance-appwidget/src/main/res/drawable/glance_component_circle_button_ripple.xml
new file mode 100644
index 0000000..5c3c1e4
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/drawable/glance_component_circle_button_ripple.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright (C) 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!-- Fixed radius ripple matching the button's outline -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/glance_component_circle_icon_button_corners"/>
+            <solid android:color="@android:color/white"/>
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/main/res/drawable/glance_component_m3_button_ripple.xml b/glance/glance-appwidget/src/main/res/drawable/glance_component_m3_button_ripple.xml
new file mode 100644
index 0000000..8150c36
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/drawable/glance_component_m3_button_ripple.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright (C) 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!-- Fixed radius ripple matching the button's outline -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/glance_component_button_corners"/>
+            <solid android:color="@android:color/white"/>
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/main/res/drawable/glance_component_square_button_ripple.xml b/glance/glance-appwidget/src/main/res/drawable/glance_component_square_button_ripple.xml
new file mode 100644
index 0000000..db64c61
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/drawable/glance_component_square_button_ripple.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright (C) 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!-- Fixed radius ripple matching the button's outline -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/glance_component_square_icon_button_corners"/>
+            <solid android:color="@android:color/white"/>
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/main/res/values/glance_component_dimens.xml b/glance/glance-appwidget/src/main/res/values/glance_component_dimens.xml
new file mode 100644
index 0000000..5a4c16b
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/values/glance_component_dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <dimen name="glance_component_button_corners">24dp</dimen>
+    <dimen name="glance_component_square_icon_button_corners">16dp</dimen>
+    <dimen name="glance_component_circle_icon_button_corners">9999.dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/AppWidgetSessionTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/AppWidgetSessionTest.kt
index 411726c..1558cce 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/AppWidgetSessionTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/AppWidgetSessionTest.kt
@@ -123,15 +123,17 @@
 
     @Test
     fun processEmittableTree_catchesException() = runTest {
-        val root = RemoteViewsRoot(maxDepth = 1).apply {
-            children += object : Emittable {
-                override var modifier: GlanceModifier = GlanceModifier
-                override fun copy() = this
+        widget.withErrorLayout(R.layout.glance_error_layout) {
+            val root = RemoteViewsRoot(maxDepth = 1).apply {
+                children += object : Emittable {
+                    override var modifier: GlanceModifier = GlanceModifier
+                    override fun copy() = this
+                }
             }
-        }
 
-        session.processEmittableTree(context, root)
-        assertThat(session.lastRemoteViews!!.layoutId).isEqualTo(widget.errorUiLayout)
+            session.processEmittableTree(context, root)
+            assertThat(session.lastRemoteViews!!.layoutId).isEqualTo(R.layout.glance_error_layout)
+        }
     }
 
     @Test
@@ -207,6 +209,19 @@
         assertTrue(didRunSecond)
     }
 
+    @Test
+    fun onCompositionError() = runTest {
+        // Session should rethrow the error when widget.errorUiLayout == 0
+        val throwable = Exception("error")
+        var caught: Throwable? = null
+        try {
+            session.onCompositionError(context, throwable)
+        } catch (t: Throwable) {
+            caught = t
+        }
+        assertThat(caught).isEqualTo(throwable)
+    }
+
     private class TestGlanceState : ConfigManager {
 
         val getValueCalls = mutableListOf<String>()
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/TestUtils.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/TestUtils.kt
index 2be2889..0c6740c 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/TestUtils.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/TestUtils.kt
@@ -176,7 +176,9 @@
 internal class TestWidget(
     override val sizeMode: SizeMode = SizeMode.Single,
     val ui: @Composable () -> Unit,
-) : GlanceAppWidget() {
+) : GlanceAppWidget(errorUiLayout = 0) {
+    override var errorUiLayout: Int = 0
+
     val provideGlanceCalled = AtomicBoolean(false)
     override suspend fun provideGlance(
         context: Context,
@@ -185,6 +187,16 @@
         provideGlanceCalled.set(true)
         provideContent(ui)
     }
+
+    inline fun withErrorLayout(layout: Int, block: () -> Unit) {
+        val previousErrorLayout = errorUiLayout
+        errorUiLayout = layout
+        try {
+            block()
+        } finally {
+            errorUiLayout = previousErrorLayout
+        }
+    }
 }
 
 /** Count the number of children that are not gone. */
diff --git a/glance/glance-template/lint-baseline.xml b/glance/glance-template/lint-baseline.xml
new file mode 100644
index 0000000..8a73870
--- /dev/null
+++ b/glance/glance-template/lint-baseline.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.2.0-alpha15" type="baseline" client="cli" dependencies="false" name="AGP (8.2.0-alpha15)" variant="all" version="8.2.0-alpha15">
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        textList.forEachIndexed { index, item ->"
+        errorLine2="                 ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/template/GlanceAppWidgetTemplates.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            actionBlock.actionButtons.forEach { button ->"
+        errorLine2="                                      ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/template/GlanceAppWidgetTemplates.kt"/>
+    </issue>
+
+</issues>
diff --git a/glance/glance-template/src/main/java/androidx/glance/template/GlanceAppWidgetTemplates.kt b/glance/glance-template/src/main/java/androidx/glance/template/GlanceAppWidgetTemplates.kt
index 9937ec6..d5804bd 100644
--- a/glance/glance-template/src/main/java/androidx/glance/template/GlanceAppWidgetTemplates.kt
+++ b/glance/glance-template/src/main/java/androidx/glance/template/GlanceAppWidgetTemplates.kt
@@ -104,7 +104,6 @@
  *
  * @param textList the ordered list of text fields to display in the block
  */
-@Suppress("ListIterator")
 @Composable
 internal fun AppWidgetTextSection(textList: List<TemplateText>) {
     if (textList.isEmpty()) return
@@ -218,7 +217,6 @@
  *
  * @param actionBlock The [ActionBlock] data containing a list of buttons for display
  */
-@Suppress("ListIterator")
 @Composable
 internal fun ActionBlockTemplate(actionBlock: ActionBlock?) {
     if (actionBlock?.actionButtons?.isNotEmpty() == true) {
diff --git a/glance/glance-testing/api/current.txt b/glance/glance-testing/api/current.txt
index f12cf50..7524595 100644
--- a/glance/glance-testing/api/current.txt
+++ b/glance/glance-testing/api/current.txt
@@ -12,16 +12,29 @@
     method public androidx.glance.testing.GlanceNodeAssertion<R,T> assert(androidx.glance.testing.GlanceNodeMatcher<R> matcher, optional kotlin.jvm.functions.Function0<java.lang.String>? messagePrefixOnError);
     method public androidx.glance.testing.GlanceNodeAssertion<R,T> assertDoesNotExist();
     method public androidx.glance.testing.GlanceNodeAssertion<R,T> assertExists();
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> onChildren();
+  }
+
+  public final class GlanceNodeAssertionCollection<R, T extends androidx.glance.testing.GlanceNode<R>> {
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertAll(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertAny(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertCountEquals(int expectedCount);
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> filter(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+    method public operator androidx.glance.testing.GlanceNodeAssertion<R,T> get(int index);
   }
 
   public interface GlanceNodeAssertionsProvider<R, T extends androidx.glance.testing.GlanceNode<R>> {
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> onAllNodes(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
     method public androidx.glance.testing.GlanceNodeAssertion<R,T> onNode(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
   }
 
   public final class GlanceNodeMatcher<R> {
     ctor public GlanceNodeMatcher(String description, kotlin.jvm.functions.Function1<? super androidx.glance.testing.GlanceNode<R>,java.lang.Boolean> matcher);
+    method public infix androidx.glance.testing.GlanceNodeMatcher<R> and(androidx.glance.testing.GlanceNodeMatcher<R> other);
     method public boolean matches(androidx.glance.testing.GlanceNode<R> node);
     method public boolean matchesAny(Iterable<? extends androidx.glance.testing.GlanceNode<R>> nodes);
+    method public operator androidx.glance.testing.GlanceNodeMatcher<R> not();
+    method public infix androidx.glance.testing.GlanceNodeMatcher<R> or(androidx.glance.testing.GlanceNodeMatcher<R> other);
   }
 
 }
@@ -40,8 +53,9 @@
   public final class UnitTestAssertionExtensionsKt {
     method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
     method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value);
-    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean substring);
-    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean substring, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescriptionEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescriptionEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean ignoreCase);
     method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasNoClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
     method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.ComponentName componentName);
     method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
@@ -49,13 +63,19 @@
     method public static <T extends android.app.Activity> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, Class<T> activityClass, optional androidx.glance.action.ActionParameters parameters);
     method public static <T extends android.app.Activity> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, Class<T> activityClass, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
     method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasTestTag(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String testTag);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasText(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasText(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasTextEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasTextEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text, optional boolean ignoreCase);
   }
 
   public final class UnitTestFiltersKt {
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasAnyDescendant(androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> matcher);
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasClickAction();
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value);
-    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value, optional boolean substring);
-    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value, optional boolean substring, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescriptionEqualTo(String value);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescriptionEqualTo(String value, optional boolean ignoreCase);
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasNoClickAction();
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(android.content.ComponentName componentName);
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
@@ -64,8 +84,9 @@
     method public static <T extends android.app.Activity> androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(Class<T> activityClass, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasTestTag(String testTag);
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text);
-    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text, optional boolean substring);
-    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text, optional boolean substring, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasTextEqualTo(String text);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasTextEqualTo(String text, optional boolean ignoreCase);
   }
 
 }
diff --git a/glance/glance-testing/api/restricted_current.txt b/glance/glance-testing/api/restricted_current.txt
index f12cf50..7524595 100644
--- a/glance/glance-testing/api/restricted_current.txt
+++ b/glance/glance-testing/api/restricted_current.txt
@@ -12,16 +12,29 @@
     method public androidx.glance.testing.GlanceNodeAssertion<R,T> assert(androidx.glance.testing.GlanceNodeMatcher<R> matcher, optional kotlin.jvm.functions.Function0<java.lang.String>? messagePrefixOnError);
     method public androidx.glance.testing.GlanceNodeAssertion<R,T> assertDoesNotExist();
     method public androidx.glance.testing.GlanceNodeAssertion<R,T> assertExists();
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> onChildren();
+  }
+
+  public final class GlanceNodeAssertionCollection<R, T extends androidx.glance.testing.GlanceNode<R>> {
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertAll(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertAny(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> assertCountEquals(int expectedCount);
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> filter(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
+    method public operator androidx.glance.testing.GlanceNodeAssertion<R,T> get(int index);
   }
 
   public interface GlanceNodeAssertionsProvider<R, T extends androidx.glance.testing.GlanceNode<R>> {
+    method public androidx.glance.testing.GlanceNodeAssertionCollection<R,T> onAllNodes(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
     method public androidx.glance.testing.GlanceNodeAssertion<R,T> onNode(androidx.glance.testing.GlanceNodeMatcher<R> matcher);
   }
 
   public final class GlanceNodeMatcher<R> {
     ctor public GlanceNodeMatcher(String description, kotlin.jvm.functions.Function1<? super androidx.glance.testing.GlanceNode<R>,java.lang.Boolean> matcher);
+    method public infix androidx.glance.testing.GlanceNodeMatcher<R> and(androidx.glance.testing.GlanceNodeMatcher<R> other);
     method public boolean matches(androidx.glance.testing.GlanceNode<R> node);
     method public boolean matchesAny(Iterable<? extends androidx.glance.testing.GlanceNode<R>> nodes);
+    method public operator androidx.glance.testing.GlanceNodeMatcher<R> not();
+    method public infix androidx.glance.testing.GlanceNodeMatcher<R> or(androidx.glance.testing.GlanceNodeMatcher<R> other);
   }
 
 }
@@ -40,8 +53,9 @@
   public final class UnitTestAssertionExtensionsKt {
     method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
     method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value);
-    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean substring);
-    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean substring, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescription(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescriptionEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasContentDescriptionEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String value, optional boolean ignoreCase);
     method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasNoClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>);
     method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.ComponentName componentName);
     method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
@@ -49,13 +63,19 @@
     method public static <T extends android.app.Activity> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, Class<T> activityClass, optional androidx.glance.action.ActionParameters parameters);
     method public static <T extends android.app.Activity> androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasStartActivityClickAction(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, Class<T> activityClass, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
     method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasTestTag(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String testTag);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasText(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasText(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasTextEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text);
+    method public static androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode> assertHasTextEqualTo(androidx.glance.testing.GlanceNodeAssertion<androidx.glance.testing.unit.MappedNode,androidx.glance.testing.unit.GlanceMappedNode>, String text, optional boolean ignoreCase);
   }
 
   public final class UnitTestFiltersKt {
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasAnyDescendant(androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> matcher);
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasClickAction();
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value);
-    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value, optional boolean substring);
-    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value, optional boolean substring, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescription(String value, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescriptionEqualTo(String value);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasContentDescriptionEqualTo(String value, optional boolean ignoreCase);
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasNoClickAction();
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(android.content.ComponentName componentName);
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(android.content.ComponentName componentName, optional androidx.glance.action.ActionParameters parameters);
@@ -64,8 +84,9 @@
     method public static <T extends android.app.Activity> androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasStartActivityClickAction(Class<T> activityClass, optional androidx.glance.action.ActionParameters parameters, optional android.os.Bundle? activityOptions);
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasTestTag(String testTag);
     method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text);
-    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text, optional boolean substring);
-    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text, optional boolean substring, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasText(String text, optional boolean ignoreCase);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasTextEqualTo(String text);
+    method public static androidx.glance.testing.GlanceNodeMatcher<androidx.glance.testing.unit.MappedNode> hasTextEqualTo(String text, optional boolean ignoreCase);
   }
 
 }
diff --git a/glance/glance-testing/lint-baseline.xml b/glance/glance-testing/lint-baseline.xml
new file mode 100644
index 0000000..bc6854b
--- /dev/null
+++ b/glance/glance-testing/lint-baseline.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.2.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-beta01)" variant="all" version="8.2.0-beta01">
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    nodes.forEachIndexed { index, glanceNode ->"
+        errorLine2="          ~~~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/testing/AssertionErrorMessages.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        children.forEach { child ->"
+        errorLine2="                 ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/testing/unit/GlanceMappedNode.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        return mappedNodes.toList()"
+        errorLine2="                           ~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/testing/unit/GlanceMappedNode.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        val violations = filteredNodes.filter {"
+        errorLine2="                                       ~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/testing/GlanceNodeAssertionCollection.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            this.allNodes = allNodes.toList()"
+        errorLine2="                                     ~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/testing/TestContext.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            ?.joinToString()"
+        errorLine2="              ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/testing/unit/UnitTestFilters.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        return node.children().any { checkIfSubtreeMatchesRecursive(matcher, it) }"
+        errorLine2="                               ~~~">
+        <location
+            file="src/main/java/androidx/glance/testing/unit/UnitTestFilters.kt"/>
+    </issue>
+
+</issues>
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/AssertionErrorMessages.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/AssertionErrorMessages.kt
index 671a692..16155f6 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/AssertionErrorMessages.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/AssertionErrorMessages.kt
@@ -19,23 +19,40 @@
 import java.lang.StringBuilder
 
 /**
- * Builds error message for case where expected amount of matching nodes does not match reality.
+ * Builds error message with reason appended.
  *
- * Provide [errorMessage] to explain which operation you were about to perform. This makes it
- * easier for developer to find where the failure happened.
+ * @param errorMessageOnFail message explaining which operation you were about to perform. This
+ *                           makes it easier for developer to find where the failure happened.
+ * @param reason the reason for failure
  */
-internal fun buildErrorMessageForCountMismatch(
-    errorMessage: String,
+internal fun buildErrorMessageWithReason(errorMessageOnFail: String, reason: String): String {
+    return "${errorMessageOnFail}\nReason: $reason"
+}
+
+/**
+ * Builds error reason for case where amount of matching nodes are less than needed to query given
+ * index and perform assertions on (e.g. if getting a node at index 2 but only 2 nodes exist in
+ * the collection).
+ */
+internal fun buildErrorReasonForIndexOutOfMatchedNodeBounds(
+    matcherDescription: String,
+    requestedIndex: Int,
+    actualCount: Int
+): String {
+    return "Not enough node(s) matching condition: ($matcherDescription) " +
+        "to get node at index '$requestedIndex'. Found '$actualCount' matching node(s)"
+}
+
+/**
+ * Builds error reason for case where expected amount of matching nodes does not match reality.
+ */
+internal fun buildErrorReasonForCountMismatch(
     matcherDescription: String,
     expectedCount: Int,
     actualCount: Int
 ): String {
     val sb = StringBuilder()
 
-    sb.append(errorMessage)
-    sb.append("\n")
-
-    sb.append("Reason: ")
     when (expectedCount) {
         0 -> {
             sb.append("Did not expect any node matching condition: $matcherDescription")
@@ -52,20 +69,54 @@
 }
 
 /**
- * Builds error message for general assertion errors.
+ * Builds error reason for assertions where at least one node was expected to be present to make
+ * assertions on (e.g. assertAny).
+ */
+internal fun buildErrorReasonForAtLeastOneNodeExpected(
+    matcherDescription: String
+): String {
+    return "Expected to receive at least 1 node " +
+        "but 0 nodes were found for condition: ($matcherDescription)"
+}
+
+/**
+ * Builds error message for general assertion errors involving a single node.
  *
  * <p>Provide [errorMessage] to explain which operation you were about to perform. This makes it
  * easier for developer to find where the failure happened.
  */
 internal fun <R> buildGeneralErrorMessage(
     errorMessage: String,
-    glanceNode: GlanceNode<R>
+    node: GlanceNode<R>
 ): String {
     val sb = StringBuilder()
     sb.append(errorMessage)
 
     sb.append("\n")
-    sb.append("Glance Node: ${glanceNode.toDebugString()}")
+    sb.append("Glance Node: ${node.toDebugString()}")
+
+    return sb.toString()
+}
+
+/**
+ * Builds error message for general assertion errors for multiple nodes.
+ *
+ * <p>Provide [errorMessage] to explain which operation you were about to perform. This makes it
+ * easier for developer to find where the failure happened.
+ */
+internal fun <R> buildGeneralErrorMessage(
+    errorMessage: String,
+    nodes: List<GlanceNode<R>>
+): String {
+    val sb = StringBuilder()
+    sb.append(errorMessage)
+
+    sb.append("\n")
+    sb.append("Found ${nodes.size} node(s) that don't match.")
+
+    nodes.forEachIndexed { index, glanceNode ->
+        sb.append("\nNon-matching Glance Node #${index + 1}: ${glanceNode.toDebugString()}")
+    }
 
     return sb.toString()
 }
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertion.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertion.kt
index ed47355..4095626 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertion.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertion.kt
@@ -22,12 +22,12 @@
 /**
  * Represents a Glance node from the tree that can be asserted on.
  *
- * An instance of [GlanceNodeAssertion] can be obtained from {@code onNode} and equivalent methods
- * on a GlanceNodeAssertionProvider
+ * An instance of [GlanceNodeAssertion] can be obtained from `onNode` and equivalent methods
+ * on a [GlanceNodeAssertionsProvider]
  */
 class GlanceNodeAssertion<R, T : GlanceNode<R>> @RestrictTo(Scope.LIBRARY_GROUP) constructor(
-    private val matcher: GlanceNodeMatcher<R>,
     private val testContext: TestContext<R, T>,
+    private val selector: GlanceNodeSelector<R>,
 ) {
     /**
      * Asserts that the node was found.
@@ -35,7 +35,7 @@
      * @throws [AssertionError] if the assert fails.
      */
     fun assertExists(): GlanceNodeAssertion<R, T> {
-        findSingleMatchingNode(finalErrorMessageOnFail = "Failed assertExists")
+        findSingleMatchingNode(errorMessageOnFail = "Failed assertExists")
         return this
     }
 
@@ -45,14 +45,17 @@
      * @throws [AssertionError] if the assert fails.
      */
     fun assertDoesNotExist(): GlanceNodeAssertion<R, T> {
-        val matchedNodesCount = findMatchingNodes().size
+        val errorMessageOnFail = "Failed assertDoesNotExist"
+        val matchedNodesCount = testContext.findMatchingNodes(selector, errorMessageOnFail).size
         if (matchedNodesCount != 0) {
             throw AssertionError(
-                buildErrorMessageForCountMismatch(
-                    errorMessage = "Failed assertDoesNotExist",
-                    matcherDescription = matcher.description,
-                    expectedCount = 0,
-                    actualCount = matchedNodesCount
+                buildErrorMessageWithReason(
+                    errorMessageOnFail = errorMessageOnFail,
+                    reason = buildErrorReasonForCountMismatch(
+                        matcherDescription = selector.description,
+                        expectedCount = 0,
+                        actualCount = matchedNodesCount
+                    )
                 )
             )
         }
@@ -93,40 +96,28 @@
         return this
     }
 
-    private fun findSingleMatchingNode(finalErrorMessageOnFail: String): GlanceNode<R> {
-        val matchingNodes = findMatchingNodes()
-        val matchedNodesCount = matchingNodes.size
-        if (matchedNodesCount != 1) {
+    /**
+     * Returns [GlanceNodeAssertionCollection] that allows performing assertions on the children of
+     * the node selected by this [GlanceNodeAssertion].
+     */
+    fun onChildren(): GlanceNodeAssertionCollection<R, T> {
+        return GlanceNodeAssertionCollection(testContext, selector.addChildrenSelector())
+    }
+
+    private fun findSingleMatchingNode(errorMessageOnFail: String): GlanceNode<R> {
+        val matchingNodes = testContext.findMatchingNodes(selector, errorMessageOnFail)
+        if (matchingNodes.size != 1) {
             throw AssertionError(
-                buildErrorMessageForCountMismatch(
-                    finalErrorMessageOnFail,
-                    matcher.description,
-                    expectedCount = 1,
-                    actualCount = matchedNodesCount
+                buildErrorMessageWithReason(
+                    errorMessageOnFail = errorMessageOnFail,
+                    reason = buildErrorReasonForCountMismatch(
+                        matcherDescription = selector.description,
+                        expectedCount = 1,
+                        actualCount = matchingNodes.size
+                    )
                 )
             )
         }
         return matchingNodes.single()
     }
-
-    private fun findMatchingNodes(): List<GlanceNode<R>> {
-        if (testContext.cachedMatchedNodes.isEmpty()) {
-            val rootGlanceNode =
-                checkNotNull(testContext.rootGlanceNode) { "No root GlanceNode found." }
-            testContext.cachedMatchedNodes = findMatchingNodes(rootGlanceNode)
-        }
-        return testContext.cachedMatchedNodes
-    }
-
-    @Suppress("ListIterator")
-    private fun findMatchingNodes(node: GlanceNode<R>): List<GlanceNode<R>> {
-        val matching = mutableListOf<GlanceNode<R>>()
-        if (matcher.matches(node)) {
-            matching.add(node)
-        }
-        for (child in node.children()) {
-            matching.addAll(findMatchingNodes(child))
-        }
-        return matching.toList()
-    }
 }
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertionCollection.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertionCollection.kt
new file mode 100644
index 0000000..cbe4572
--- /dev/null
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertionCollection.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.glance.testing
+
+import androidx.annotation.RestrictTo
+
+/**
+ * Represents a collection of Glance nodes from the tree that can be asserted on.
+ *
+ * An instance of [GlanceNodeAssertionCollection] can be obtained from
+ * [GlanceNodeAssertionsProvider.onAllNodes] and equivalent methods.
+ */
+// Equivalent to SemanticsNodeInteractionCollection in compose.
+class GlanceNodeAssertionCollection<R, T : GlanceNode<R>>
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) constructor(
+    private val testContext: TestContext<R, T>,
+    private val selector: GlanceNodeSelector<R>
+) {
+    /**
+     * Asserts that this collection of nodes is equal to the given [expectedCount].
+     *
+     * @throws AssertionError if the size is not equal to [expectedCount]
+     */
+    fun assertCountEquals(
+        expectedCount: Int
+    ): GlanceNodeAssertionCollection<R, T> {
+        val errorMessageOnFail = "Failed to assert count of nodes"
+
+        val actualCount = testContext.findMatchingNodes(selector, errorMessageOnFail).size
+        if (actualCount != expectedCount) {
+            throw AssertionError(
+                buildErrorMessageWithReason(
+                    errorMessageOnFail = errorMessageOnFail,
+                    reason = buildErrorReasonForCountMismatch(
+                        matcherDescription = selector.description,
+                        expectedCount = expectedCount,
+                        actualCount = actualCount
+                    )
+                )
+            )
+        }
+        return this
+    }
+
+    /**
+     * Asserts that all the nodes in this collection satisfy the given [matcher].
+     *
+     * Doesn't throw error if the collection is empty. Use [assertCountEquals] to assert on expected
+     * size of the collection.
+     *
+     * @param matcher Matcher that has to be satisfied by all the nodes in the collection.
+     * @throws AssertionError if the collection contains at least one element that does not satisfy
+     * the given matcher.
+     */
+    fun assertAll(
+        matcher: GlanceNodeMatcher<R>,
+    ): GlanceNodeAssertionCollection<R, T> {
+        val errorMessageOnFail = "Failed to assertAll(${matcher.description})"
+
+        val filteredNodes = testContext.findMatchingNodes(selector, errorMessageOnFail)
+        val violations = filteredNodes.filter {
+            !matcher.matches(it)
+        }
+        if (violations.isNotEmpty()) {
+            throw AssertionError(buildGeneralErrorMessage(errorMessageOnFail, violations))
+        }
+        return this
+    }
+
+    /**
+     * Asserts that this collection contains at least one element that satisfies the given
+     * [matcher].
+     *
+     * @param matcher Matcher that has to be satisfied by at least one of the nodes in the
+     * collection.
+     * @throws AssertionError if not at least one matching node was found.
+     */
+    fun assertAny(
+        matcher: GlanceNodeMatcher<R>,
+    ): GlanceNodeAssertionCollection<R, T> {
+        val errorMessageOnFail = "Failed to assertAny(${matcher.description})"
+        val filteredNodes = testContext.findMatchingNodes(selector, errorMessageOnFail)
+
+        if (filteredNodes.isEmpty()) {
+            throw AssertionError(
+                buildErrorMessageWithReason(
+                    errorMessageOnFail = errorMessageOnFail,
+                    reason = buildErrorReasonForAtLeastOneNodeExpected(selector.description)
+                )
+            )
+        }
+
+        if (!matcher.matchesAny(filteredNodes)) {
+            throw AssertionError(buildGeneralErrorMessage(errorMessageOnFail, filteredNodes))
+        }
+        return this
+    }
+
+    /**
+     * Returns a [GlanceNodeAssertion] that can assert on the node at the given index of this
+     * collection.
+     *
+     * Any subsequent assertion on its result will throw error if index is out of bounds of the
+     * matching nodes found from previous operations.
+     */
+    operator fun get(index: Int): GlanceNodeAssertion<R, T> {
+        return GlanceNodeAssertion(
+            testContext = testContext,
+            selector = selector.addIndexedSelector(index)
+        )
+    }
+
+    /**
+     * Returns a new collection of nodes by filtering the given nodes using the provided [matcher].
+     */
+    fun filter(matcher: GlanceNodeMatcher<R>): GlanceNodeAssertionCollection<R, T> {
+        return GlanceNodeAssertionCollection(
+            testContext,
+            selector.addMatcherSelector(
+                selectorName = "filter",
+                matcher = matcher
+            )
+        )
+    }
+}
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertionsProvider.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertionsProvider.kt
index 624b529..8adddc0 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertionsProvider.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertionsProvider.kt
@@ -30,4 +30,11 @@
      * @param matcher Matcher used for filtering
      */
     fun onNode(matcher: GlanceNodeMatcher<R>): GlanceNodeAssertion<R, T>
+
+    /**
+     * Finds all Glance nodes that matches the given condition.
+     *
+     * @param matcher Matcher used for filtering
+     */
+    fun onAllNodes(matcher: GlanceNodeMatcher<R>): GlanceNodeAssertionCollection<R, T>
 }
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeMatcher.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeMatcher.kt
index 0e9fe2a..6de4dc5 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeMatcher.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeMatcher.kt
@@ -38,4 +38,35 @@
     fun matchesAny(nodes: Iterable<GlanceNode<R>>): Boolean {
         return nodes.any(matcher)
     }
+
+    /**
+     * Returns whether the given node is matched by this and the [other] matcher.
+     *
+     * @param other matcher that should also match in addition to current matcher
+     */
+    infix fun and(other: GlanceNodeMatcher<R>): GlanceNodeMatcher<R> {
+        return GlanceNodeMatcher("($description) && (${other.description})") {
+            matcher(it) && other.matches(it)
+        }
+    }
+
+    /**
+     * Returns whether the given node is matched by this or the [other] matcher.
+     *
+     * @param other matcher that can be tested to match if the current matcher doesn't.
+     */
+    infix fun or(other: GlanceNodeMatcher<R>): GlanceNodeMatcher<R> {
+        return GlanceNodeMatcher("($description) || (${other.description})") {
+            matcher(it) || other.matches(it)
+        }
+    }
+
+    /**
+     * Returns whether the given node does not match the matcher.
+     */
+    operator fun not(): GlanceNodeMatcher<R> {
+        return GlanceNodeMatcher(("NOT ($description)")) {
+            !matcher(it)
+        }
+    }
 }
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeSelector.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeSelector.kt
new file mode 100644
index 0000000..f06d499
--- /dev/null
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeSelector.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.glance.testing
+
+import androidx.annotation.RestrictTo
+
+/**
+ * A chainable selector that allows specifying how to select nodes from a collection.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class GlanceNodeSelector<R>(
+    val description: String,
+    private val previousChainedSelector: GlanceNodeSelector<R>? = null,
+    private val selector: (Iterable<GlanceNode<R>>) -> SelectionResult<R>
+) {
+
+    /**
+     * Returns nodes selected by previous chained selectors followed by the current selector.
+     */
+    fun map(nodes: Iterable<GlanceNode<R>>): SelectionResult<R> {
+        val previousSelectionResult = previousChainedSelector?.map(nodes)
+        val inputNodes = previousSelectionResult?.selectedNodes ?: nodes
+        return selector(inputNodes)
+    }
+}
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class SelectionResult<R>(
+    val selectedNodes: List<GlanceNode<R>>,
+    val errorMessageOnNoMatch: String? = null
+)
+
+/**
+ * Constructs an entry-point selector that selects nodes satisfying the matcher condition. Used at
+ * the entry points such as [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] where there is no previous chained selector.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun <R> GlanceNodeMatcher<R>.matcherToSelector(): GlanceNodeSelector<R> {
+    return GlanceNodeSelector(
+        description = description,
+        previousChainedSelector = null
+    ) { glanceNodes ->
+        SelectionResult(
+            selectedNodes = glanceNodes.filter { matches(it) }
+        )
+    }
+}
+
+/**
+ * Wraps the current selector with a chained selector that selects a node at a given index from the
+ * the result of current selection.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun <R> GlanceNodeSelector<R>.addIndexedSelector(index: Int): GlanceNodeSelector<R> {
+    return GlanceNodeSelector(
+        description = "(${this.description})[$index]",
+        previousChainedSelector = this
+    ) { nodes ->
+        val nodesList = nodes.toList()
+        val minimumExpectedCount = index + 1
+        if (index >= 0 && index < nodesList.size) {
+            SelectionResult(
+                selectedNodes = listOf(nodesList[index])
+            )
+        } else {
+            SelectionResult(
+                selectedNodes = emptyList(),
+                errorMessageOnNoMatch = buildErrorReasonForIndexOutOfMatchedNodeBounds(
+                    description,
+                    requestedIndex = minimumExpectedCount,
+                    actualCount = nodesList.size
+                )
+            )
+        }
+    }
+}
+
+/**
+ * Wraps the current selector with a chained matcher-based selector that filters the list of nodes
+ * to return ones matched by the matcher.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun <R> GlanceNodeSelector<R>.addMatcherSelector(
+    selectorName: String,
+    matcher: GlanceNodeMatcher<R>
+): GlanceNodeSelector<R> {
+    return GlanceNodeSelector(
+        description = "(${this.description}).$selectorName(${matcher.description})",
+        previousChainedSelector = this
+    ) { nodes ->
+        SelectionResult(
+            selectedNodes = nodes.filter { matcher.matches(it) }
+        )
+    }
+}
+
+/**
+ * Wraps the current selector with a chained matcher-based selector that ensures only one node is
+ * returned by current selector and selects children of that node.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun <R> GlanceNodeSelector<R>.addChildrenSelector(): GlanceNodeSelector<R> {
+    return GlanceNodeSelector(
+        description = "($description).children()",
+        previousChainedSelector = this
+    ) { nodes ->
+        if (nodes.count() != 1) {
+            SelectionResult(
+                selectedNodes = emptyList(),
+                errorMessageOnNoMatch = buildErrorReasonForCountMismatch(
+                    matcherDescription = description,
+                    expectedCount = 1,
+                    actualCount = nodes.count()
+                )
+            )
+        } else {
+            SelectionResult(
+                selectedNodes = nodes.single().children()
+            )
+        }
+    }
+}
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/TestContext.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/TestContext.kt
index 1ab76a3..419a304 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/TestContext.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/TestContext.kt
@@ -23,13 +23,55 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 class TestContext<R, T : GlanceNode<R>> {
+    var rootGlanceNode: T? = null
+    private var allNodes: List<GlanceNode<R>> = emptyList()
+
     /**
-     * To be called on every onNode to restart matching and clear cache.
+     * Returns all nodes in single flat list (either from cache or by traversing the hierarchy from
+     * root glance node).
      */
-    fun reset() {
-        cachedMatchedNodes = emptyList()
+    private fun getAllNodes(): List<GlanceNode<R>> {
+        val rootGlanceNode =
+            checkNotNull(rootGlanceNode) { "No root GlanceNode found." }
+        if (this.allNodes.isEmpty()) {
+            val allNodes = mutableListOf<GlanceNode<R>>()
+
+            fun collectAllNodesRecursive(currentNode: GlanceNode<R>) {
+                allNodes.add(currentNode)
+                val children = currentNode.children()
+                for (index in children.indices) {
+                    collectAllNodesRecursive(children[index])
+                }
+            }
+
+            collectAllNodesRecursive(rootGlanceNode)
+            this.allNodes = allNodes.toList()
+        }
+
+        return this.allNodes
     }
 
-    var rootGlanceNode: T? = null
-    var cachedMatchedNodes: List<GlanceNode<R>> = emptyList()
+    /**
+     * Finds nodes matching the given selector from the list of all nodes in the hierarchy.
+     *
+     * @throws AssertionError if provided selector results in an error due to no match.
+     */
+    fun findMatchingNodes(
+        selector: GlanceNodeSelector<R>,
+        errorMessageOnFail: String
+    ): List<GlanceNode<R>> {
+        val allNodes = getAllNodes()
+        val selectionResult = selector.map(allNodes)
+
+        if (selectionResult.errorMessageOnNoMatch != null) {
+            throw AssertionError(
+                buildErrorMessageWithReason(
+                    errorMessageOnFail = errorMessageOnFail,
+                    reason = selectionResult.errorMessageOnNoMatch
+                )
+            )
+        }
+
+        return selectionResult.selectedNodes
+    }
 }
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/unit/GlanceMappedNode.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/unit/GlanceMappedNode.kt
index 0ac92ee..929fc80 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/unit/GlanceMappedNode.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/unit/GlanceMappedNode.kt
@@ -19,6 +19,7 @@
 import androidx.annotation.RestrictTo
 import androidx.annotation.RestrictTo.Scope
 import androidx.glance.Emittable
+import androidx.glance.EmittableLazyItemWithChildren
 import androidx.glance.EmittableWithChildren
 import androidx.glance.testing.GlanceNode
 
@@ -59,15 +60,24 @@
     @RestrictTo(Scope.LIBRARY_GROUP)
     override fun children(): List<GlanceNode<MappedNode>> {
         val emittable = mappedNode.emittable
-        @Suppress("ListIterator")
         if (emittable is EmittableWithChildren) {
-            return emittable.children.map { child ->
-                GlanceMappedNode(child)
-            }
+            return emittable.toMappedNodes()
         }
         return emptyList()
     }
 
+    private fun EmittableWithChildren.toMappedNodes(): List<GlanceMappedNode> {
+        val mappedNodes = mutableListOf<GlanceMappedNode>()
+        children.forEach { child ->
+            if (child is EmittableLazyItemWithChildren) {
+                mappedNodes.addAll(child.toMappedNodes())
+            } else {
+                mappedNodes.add(GlanceMappedNode(child))
+            }
+        }
+        return mappedNodes.toList()
+    }
+
     @RestrictTo(Scope.LIBRARY_GROUP)
     override fun toDebugString(): String {
         // TODO(b/201779038): map to a more readable format.
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/unit/TestUtils.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/unit/TestUtils.kt
index 40539aa..17935b0 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/unit/TestUtils.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/unit/TestUtils.kt
@@ -19,9 +19,12 @@
 import androidx.annotation.RestrictTo
 import androidx.glance.Emittable
 import androidx.glance.testing.GlanceNodeAssertion
+import androidx.glance.testing.GlanceNodeAssertionCollection
 import androidx.glance.testing.GlanceNodeMatcher
 import androidx.glance.testing.TestContext
+import androidx.glance.testing.matcherToSelector
 
+// Equivalent to calling GlanceNodeAssertionsProvider.onNode
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 fun getGlanceNodeAssertionFor(
     emittable: Emittable,
@@ -30,7 +33,21 @@
     val testContext = TestContext<MappedNode, GlanceMappedNode>()
     testContext.rootGlanceNode = GlanceMappedNode(emittable)
     return GlanceNodeAssertion(
-        matcher = onNodeMatcher,
-        testContext = testContext
+        testContext = testContext,
+        selector = onNodeMatcher.matcherToSelector()
+    )
+}
+
+// Equivalent to calling GlanceNodeAssertionsProvider.onAllNodes
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun getGlanceNodeAssertionCollectionFor(
+    emittable: Emittable,
+    onAllNodesMatcher: GlanceNodeMatcher<MappedNode>
+): GlanceNodeAssertionCollection<MappedNode, GlanceMappedNode> {
+    val testContext = TestContext<MappedNode, GlanceMappedNode>()
+    testContext.rootGlanceNode = GlanceMappedNode(emittable)
+    return GlanceNodeAssertionCollection(
+        testContext = testContext,
+        selector = onAllNodesMatcher.matcherToSelector()
     )
 }
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/unit/UnitTestAssertionExtensions.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/unit/UnitTestAssertionExtensions.kt
index 2aa1aca..715910f 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/unit/UnitTestAssertionExtensions.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/unit/UnitTestAssertionExtensions.kt
@@ -30,6 +30,35 @@
 internal typealias UnitTestAssertion = GlanceNodeAssertion<MappedNode, GlanceMappedNode>
 
 /**
+ * Asserts that text on the given node is a text node and its text contains the provided [text] as
+ * substring.
+ *
+ * @param text value to match.
+ * @param ignoreCase whether to perform case insensitive matching
+ */
+@JvmOverloads
+fun UnitTestAssertion.assertHasText(
+    text: String,
+    ignoreCase: Boolean = false
+): UnitTestAssertion {
+    return assert(hasText(text, ignoreCase))
+}
+
+/**
+ * Asserts that text on the given node is text node and its text is equal to the provided [text].
+ *
+ * @param text value to match.
+ * @param ignoreCase whether to perform case insensitive matching
+ */
+@JvmOverloads
+fun UnitTestAssertion.assertHasTextEqualTo(
+    text: String,
+    ignoreCase: Boolean = false
+): UnitTestAssertion {
+    return assert(hasTextEqualTo(text, ignoreCase))
+}
+
+/**
  * Asserts that a given node is annotated by the given test tag.
  *
  * @param testTag value to match against the free form string specified in the `testTag` semantics
@@ -41,11 +70,10 @@
 }
 
 /**
- * Asserts that a given node matches content description with the provided [value]
+ * Asserts that the content description set on the node contains the provided [value] as substring.
  *
- * @param value value to match as one of the items in the list of content descriptions.
- * @param substring whether to use substring matching.
- * @param ignoreCase whether case should be ignored.
+ * @param value value that should be matched as a substring of the node's content description.
+ * @param ignoreCase whether case should be ignored. Defaults to case sensitive.
  *
  * @see SemanticsProperties.ContentDescription
  *
@@ -54,10 +82,27 @@
 @JvmOverloads
 fun UnitTestAssertion.assertHasContentDescription(
     value: String,
-    substring: Boolean = false,
     ignoreCase: Boolean = false
 ): UnitTestAssertion {
-    return assert(hasContentDescription(value, substring, ignoreCase))
+    return assert(hasContentDescription(value, ignoreCase))
+}
+
+/**
+ * Asserts that the content description set on the node is equal to the provided [value]
+ *
+ * @param value value that should be matched to be equal to the node's content description.
+ * @param ignoreCase whether case should be ignored. Defaults to case sensitive.
+ *
+ * @see SemanticsProperties.ContentDescription
+ *
+ * @throws AssertionError if the matcher does not match or the node can no longer be found.
+ */
+@JvmOverloads
+fun UnitTestAssertion.assertHasContentDescriptionEqualTo(
+    value: String,
+    ignoreCase: Boolean = false
+): UnitTestAssertion {
+    return assert(hasContentDescriptionEqualTo(value, ignoreCase))
 }
 
 /**
@@ -81,9 +126,6 @@
 /**
  * Asserts that a given node has a clickable set with action that starts an activity.
  *
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
- * matching node(s) or in assertions to validate that node(s) satisfy the condition.
- *
  * @param activityClass class of the activity that is expected to have been passed in the
  *                      `actionStartActivity` method call
  * @param parameters the parameters associated with the action that are expected to have been passed
diff --git a/glance/glance-testing/src/main/java/androidx/glance/testing/unit/UnitTestFilters.kt b/glance/glance-testing/src/main/java/androidx/glance/testing/unit/UnitTestFilters.kt
index 577caa5..6d83d4c 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/unit/UnitTestFilters.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/unit/UnitTestFilters.kt
@@ -28,6 +28,8 @@
 import androidx.glance.semantics.SemanticsModifier
 import androidx.glance.semantics.SemanticsProperties
 import androidx.glance.semantics.SemanticsPropertyKey
+import androidx.glance.testing.GlanceNode
+import androidx.glance.testing.GlanceNodeAssertionsProvider
 import androidx.glance.testing.GlanceNodeMatcher
 
 // This file contains common filters that can be passed in "onNode", "onAllNodes" or
@@ -37,7 +39,8 @@
 /**
  * Returns a matcher that matches if a node is annotated by the given test tag.
  *
- * <p>This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
  * matching node(s) or in assertions to validate that node(s) satisfy the condition.
  *
  * @param testTag value to match against the free form string specified in the `testTag` semantics
@@ -59,31 +62,67 @@
 }
 
 /**
- * Returns whether the node matches content description with the provided [value]
+ * Returns whether the content description set directly on the node contains the provided [value].
  *
- * @param value value to match as one of the items in the list of content descriptions.
- * @param substring whether to use substring matching.
- * @param ignoreCase whether case should be ignored.
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
+ * matching node(s) or in assertions to validate that node(s) satisfy the condition.
+ *
+ * @param value value that should be substring of the content description set directly on the node.
+ * @param ignoreCase whether case should be ignored. Default is case sensitive.
  *
  * @see SemanticsProperties.ContentDescription
  */
 @JvmOverloads
 fun hasContentDescription(
     value: String,
-    substring: Boolean = false,
     ignoreCase: Boolean = false
 ): GlanceNodeMatcher<MappedNode> =
     GlanceNodeMatcher(
-        description = if (substring) {
+        description =
             "${SemanticsProperties.ContentDescription.name} contains '$value'" +
-                " (ignoreCase: '$ignoreCase')"
-        } else {
-            "${SemanticsProperties.ContentDescription.name} = '$value' (ignoreCase: '$ignoreCase')"
-        }
+                " (ignoreCase: '$ignoreCase') as substring"
     ) { node ->
         node.value.emittable.modifier.any {
             it is SemanticsModifier &&
-                hasContentDescription(it, value, substring, ignoreCase)
+                hasContentDescription(
+                    semanticsModifier = it,
+                    value = value,
+                    substring = true,
+                    ignoreCase = ignoreCase)
+        }
+    }
+
+/**
+ * Returns whether the content description set directly on the node is equal to the provided
+ * [value].
+ *
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
+ * matching node(s) or in assertions to validate that node(s) satisfy the condition.
+ *
+ * @param value value that should match exactly with content description set directly on the node.
+ * @param ignoreCase whether case should be ignored. Default is case sensitive.
+ *
+ * @see SemanticsProperties.ContentDescription
+ */
+@JvmOverloads
+fun hasContentDescriptionEqualTo(
+    value: String,
+    ignoreCase: Boolean = false
+): GlanceNodeMatcher<MappedNode> =
+    GlanceNodeMatcher(
+        description =
+        "${SemanticsProperties.ContentDescription.name} == '$value' (ignoreCase: '$ignoreCase')"
+    ) { node ->
+        node.value.emittable.modifier.any {
+            it is SemanticsModifier &&
+                hasContentDescription(
+                    semanticsModifier = it,
+                    value = value,
+                    substring = false,
+                    ignoreCase = ignoreCase
+                )
         }
     }
 
@@ -93,7 +132,6 @@
     substring: Boolean = false,
     ignoreCase: Boolean = false
 ): Boolean {
-    @Suppress("ListIterator")
     val contentDescription =
         semanticsModifier.configuration.getOrNull(SemanticsProperties.ContentDescription)
             ?.joinToString()
@@ -106,43 +144,54 @@
 }
 
 /**
- * Returns a matcher that matches if text on node matches the provided text.
+ * Returns a matcher that matches if text on node contains the provided text as its substring.
  *
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
  * matching node(s) or in assertions to validate that node(s) satisfy the condition.
  *
- * @param text value to match.
- * @param substring whether to perform substring matching
- * @param ignoreCase whether to perform case insensitive matching
+ * @param text value that should be matched as a substring of the node's text.
+ * @param ignoreCase whether to perform case insensitive matching. Defaults to case sensitive
+ *                   matching.
  */
 @JvmOverloads
 fun hasText(
     text: String,
-    substring: Boolean = false,
     ignoreCase: Boolean = false
 ): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
-    if (substring) {
-        "contains '$text' (ignoreCase: $ignoreCase) as substring"
-    } else {
-        "has text = '$text' (ignoreCase: '$ignoreCase')"
-    }
+    description = "contains text '$text' (ignoreCase: '$ignoreCase') as substring"
 ) { node ->
     val emittable = node.value.emittable
-    if (emittable is EmittableWithText) {
-        if (substring) {
-            emittable.text.contains(text, ignoreCase)
-        } else {
-            emittable.text.equals(text, ignoreCase)
-        }
-    } else {
-        false
-    }
+    emittable is EmittableWithText && emittable.text.contains(text, ignoreCase)
+}
+
+/**
+ * Returns a matcher that matches if node is a text node and its text is equal to the provided text.
+ *
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
+ * matching node(s) or in assertions to validate that node(s) satisfy the condition.
+ *
+ * @param text value that should exactly match the node's text.
+ * @param ignoreCase whether to perform case insensitive matching. Defaults to case sensitive
+ *                   matching.
+ */
+@JvmOverloads
+fun hasTextEqualTo(
+    text: String,
+    ignoreCase: Boolean = false
+): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
+    description = "text == '$text' (ignoreCase: '$ignoreCase')"
+) { node ->
+    val emittable = node.value.emittable
+    emittable is EmittableWithText && emittable.text.equals(text, ignoreCase)
 }
 
 /**
  * Returns a matcher that matches if the given node has clickable modifier set.
  *
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
  * matching node(s) or in assertions to validate that node(s) satisfy the condition.
  */
 fun hasClickAction(): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
@@ -157,7 +206,8 @@
  * Returns a matcher that matches if the given node doesn't have a clickable modifier or `onClick`
  * set.
  *
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
  * matching node(s) or in assertions to validate that node(s) satisfy the condition.
  */
 fun hasNoClickAction(): GlanceNodeMatcher<MappedNode> = GlanceNodeMatcher(
@@ -172,7 +222,8 @@
  * Returns a matcher that matches if a given node has a clickable set with action that starts an
  * activity.
  *
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
  * matching node(s) or in assertions to validate that node(s) satisfy the condition.
  *
  * @param activityClass class of the activity that is expected to have been passed in the
@@ -214,10 +265,39 @@
 }
 
 /**
+ * Returns a matcher that matches if a given node has a descendant node somewhere in its
+ * sub-hierarchy that the matches the provided matcher.
+ *
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
+ * matching node(s) or in assertions to validate that node(s) satisfy the condition.
+ *
+ * @param matcher a matcher that needs to be satisfied for the descendant node to be matched
+ */
+fun hasAnyDescendant(matcher: GlanceNodeMatcher<MappedNode>): GlanceNodeMatcher<MappedNode> {
+
+    fun checkIfSubtreeMatchesRecursive(
+        matcher: GlanceNodeMatcher<MappedNode>,
+        node: GlanceNode<MappedNode>
+    ): Boolean {
+        if (matcher.matchesAny(node.children())) {
+            return true
+        }
+
+        return node.children().any { checkIfSubtreeMatchesRecursive(matcher, it) }
+    }
+
+    return GlanceNodeMatcher("hasAnyDescendantThat(${matcher.description})") {
+        checkIfSubtreeMatchesRecursive(matcher, it)
+    }
+}
+
+/**
  * Returns a matcher that matches if a given node has a clickable set with action that starts an
  * activity.
  *
- * This can be passed in "onNode" and "onNodeAll" functions on assertion providers to filter out
+ * This can be passed in [GlanceNodeAssertionsProvider.onNode] and
+ * [GlanceNodeAssertionsProvider.onAllNodes] functions on assertion providers to filter out
  * matching node(s) or in assertions to validate that node(s) satisfy the condition.
  *
  * @param componentName component of the activity that is expected to have been passed in the
diff --git a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/AssertionErrorMessagesTest.kt b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/AssertionErrorMessagesTest.kt
index e4bebe09..e82031c 100644
--- a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/AssertionErrorMessagesTest.kt
+++ b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/AssertionErrorMessagesTest.kt
@@ -24,11 +24,13 @@
 class AssertionErrorMessagesTest {
     @Test
     fun countMismatch_expectedNone() {
-        val resultMessage = buildErrorMessageForCountMismatch(
-            errorMessage = "Failed assert",
-            matcherDescription = "testTag = 'my-node'",
-            expectedCount = 0,
-            actualCount = 1
+        val resultMessage = buildErrorMessageWithReason(
+            errorMessageOnFail = "Failed assert",
+            reason = buildErrorReasonForCountMismatch(
+                matcherDescription = "testTag = 'my-node'",
+                expectedCount = 0,
+                actualCount = 1
+            )
         )
 
         assertThat(resultMessage).isEqualTo(
@@ -40,11 +42,13 @@
 
     @Test
     fun countMismatch_expectedButFoundNone() {
-        val resultMessage = buildErrorMessageForCountMismatch(
-            errorMessage = "Failed assert",
-            matcherDescription = "testTag = 'my-node'",
-            expectedCount = 2,
-            actualCount = 0
+        val resultMessage = buildErrorMessageWithReason(
+            errorMessageOnFail = "Failed assert",
+            reason = buildErrorReasonForCountMismatch(
+                matcherDescription = "testTag = 'my-node'",
+                expectedCount = 2,
+                actualCount = 0
+            )
         )
 
         assertThat(resultMessage).isEqualTo(
@@ -55,14 +59,14 @@
     }
 
     @Test
-    fun generalErrorMessage() {
+    fun generalErrorMessage_singleNode() {
         val node = GlanceMappedNode(
             EmittableText().also { it.text = "test text" }
         )
 
         val resultMessage = buildGeneralErrorMessage(
             errorMessage = "Failed to match the condition: (testTag = 'my-node')",
-            glanceNode = node
+            node = node
         )
 
         assertThat(resultMessage).isEqualTo(
@@ -70,4 +74,26 @@
                 "\nGlance Node: ${node.toDebugString()}"
         )
     }
+
+    @Test
+    fun generalErrorMessage_multipleNodes() {
+        val node1 = GlanceMappedNode(
+            EmittableText().also { it.text = "text1" }
+        )
+        val node2 = GlanceMappedNode(
+            EmittableText().also { it.text = "text2" }
+        )
+
+        val resultMessage = buildGeneralErrorMessage(
+            errorMessage = "Failed to match the condition: (testTag = 'my-node')",
+            nodes = listOf(node1, node2)
+        )
+
+        assertThat(resultMessage).isEqualTo(
+            "Failed to match the condition: (testTag = 'my-node')" +
+                "\nFound 2 node(s) that don't match." +
+                "\nNon-matching Glance Node #1: ${node1.toDebugString()}" +
+                "\nNon-matching Glance Node #2: ${node2.toDebugString()}"
+        )
+    }
 }
diff --git a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/GlanceNodeAssertionCollectionTest.kt b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/GlanceNodeAssertionCollectionTest.kt
new file mode 100644
index 0000000..7d1202a
--- /dev/null
+++ b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/GlanceNodeAssertionCollectionTest.kt
@@ -0,0 +1,600 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.glance.testing
+
+import androidx.glance.GlanceModifier
+import androidx.glance.action.ActionModifier
+import androidx.glance.action.LambdaAction
+import androidx.glance.layout.EmittableColumn
+import androidx.glance.layout.EmittableSpacer
+import androidx.glance.semantics.semantics
+import androidx.glance.semantics.testTag
+import androidx.glance.testing.unit.assertHasText
+import androidx.glance.testing.unit.getGlanceNodeAssertionCollectionFor
+import androidx.glance.testing.unit.getGlanceNodeAssertionFor
+import androidx.glance.testing.unit.hasClickAction
+import androidx.glance.testing.unit.hasTestTag
+import androidx.glance.testing.unit.hasText
+import androidx.glance.testing.unit.hasTextEqualTo
+import androidx.glance.text.EmittableText
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Test
+
+class GlanceNodeAssertionCollectionTest {
+    @Test
+    fun assertAll_noNodesToAssertOn() {
+        // This is the object that in real usage a onAllNodes(matcher) would return.
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply { text = "some text" })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply { text = "another text" })
+            }
+        )
+
+        assertion.assertAll(hasText("substring"))
+        // no error even if no nodes with click action were available to perform assertAll; on the
+        // other hand calling assertCountEquals(x) before assertAll would have thrown an error
+    }
+
+    @Test
+    fun assertAll_allMatchingNodes() {
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "some substring text"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another substring text"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+            }
+        )
+
+        assertion.assertAll(hasText("substring"))
+        // no error
+    }
+
+    @Test
+    fun assertAll_noneMatch_throwsError() {
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "some text"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another text"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+            }
+        )
+
+        val assertionError = assertThrows(AssertionError::class.java) {
+            assertion.assertAll(hasText("substring"))
+        }
+
+        assertThat(assertionError).hasMessageThat().contains(
+            "Failed to assertAll(contains text 'substring' (ignoreCase: 'false') as substring)" +
+                "\nFound 2 node(s) that don't match."
+        )
+    }
+
+    @Test
+    fun assertAll_someNotMatch_throwsError() {
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "some substring text"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another text"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+            }
+        )
+
+        val assertionError = assertThrows(AssertionError::class.java) {
+            assertion.assertAll(hasText("substring"))
+        }
+
+        assertThat(assertionError).hasMessageThat().contains(
+            "Failed to assertAll(contains text 'substring' (ignoreCase: 'false') as substring)" +
+                "\nFound 1 node(s) that don't match."
+        )
+    }
+
+    @Test
+    fun assertAny_noNodesToAssertOn_assertionError() {
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply { text = "some text" })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply { text = "another text" })
+            }
+        )
+
+        val assertionError = assertThrows(AssertionError::class.java) {
+            assertion.assertAny(hasText("substring"))
+        }
+
+        assertThat(assertionError).hasMessageThat().contains(
+            "Failed to assertAny(contains text 'substring' (ignoreCase: 'false') as substring)" +
+                "\nReason: Expected to receive at least 1 node " +
+                "but 0 nodes were found for condition: (has click action)"
+        )
+    }
+
+    @Test
+    fun assertAny_noneMatch_assertionError() {
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "some text"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another text"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+            }
+        )
+
+        val assertionError = assertThrows(AssertionError::class.java) {
+            assertion.assertAny(hasText("expected-substring"))
+        }
+
+        assertThat(assertionError).hasMessageThat().contains(
+            "Failed to assertAny(contains text 'expected-substring' " +
+                "(ignoreCase: 'false') as substring)" +
+                "\nFound 2 node(s) that don't match."
+        )
+    }
+
+    @Test
+    fun assertAny_oneMatch() {
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "some text"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another substring text"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+            }
+        )
+
+        assertion.assertAny(hasText("substring"))
+    }
+
+    @Test
+    fun assertAny_multipleMatch() {
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "some text"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another substring text"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "yet another substring text"
+                    modifier = ActionModifier(LambdaAction("3") {})
+                })
+            }
+        )
+
+        assertion.assertAny(hasText("substring"))
+    }
+
+    @Test
+    fun assertAllAfterFilter_matchingNodes() {
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "some substring text"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another text"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "yet another substring text"
+                    modifier = ActionModifier(LambdaAction("3") {})
+                })
+            }
+        )
+
+        assertion
+            .filter(hasText("substring"))
+            .assertAll(hasText("text"))
+    }
+
+    @Test
+    fun assertAllAfterFilter_noFilteredNodes_noError() {
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "some substring text"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another text"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "yet another substring text"
+                    modifier = ActionModifier(LambdaAction("3") {})
+                })
+            }
+        )
+
+        assertion
+            .filter(hasText("word"))
+            .assertAll(hasText("text"))
+    }
+
+    @Test
+    fun assertAnyAfterFilter_matchingNodes() {
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "some substring"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another text"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "yet another substring text"
+                    modifier = ActionModifier(LambdaAction("3") {})
+                })
+            }
+        )
+
+        assertion
+            .filter(hasText("substring"))
+            .assertAny(hasText("text"))
+    }
+
+    @Test
+    fun assertAnyAfterFilter_noFilteredNodes_assertionError() {
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "some substring text"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another text"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "yet another substring text"
+                    modifier = ActionModifier(LambdaAction("3") {})
+                })
+            }
+        )
+
+        val assertionError = assertThrows(AssertionError::class.java) {
+            assertion
+                .filter(hasText("word"))
+                .assertAny(hasText("text"))
+        }
+
+        assertThat(assertionError).hasMessageThat().contains(
+            "Failed to assertAny(contains text 'text' (ignoreCase: 'false') as substring)" +
+                "\nReason: Expected to receive at least 1 node but 0 nodes were found for " +
+                "condition: " +
+                "((has click action).filter(contains text 'word' (ignoreCase: 'false') " +
+                "as substring))"
+        )
+    }
+
+    @Test
+    fun assertCountEqualsAfterFilter_matchingNodes() {
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "some substring text"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another text"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "yet another substring text"
+                    modifier = ActionModifier(LambdaAction("3") {})
+                })
+            }
+        )
+
+        assertion
+            .filter(hasText("text"))
+            .assertCountEquals(3)
+    }
+
+    @Test
+    fun assertCountEqualsAfterFilter_noFilteredNodes_assertionError() {
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "some substring text"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another text"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "yet another substring text"
+                    modifier = ActionModifier(LambdaAction("3") {})
+                })
+            }
+        )
+
+        val assertionError = assertThrows(AssertionError::class.java) {
+            assertion
+                .filter(hasText("word"))
+                .assertCountEquals(1)
+        }
+
+        assertThat(assertionError).hasMessageThat().contains(
+            "Failed to assert count of nodes" +
+                "\nReason: Expected '1' node(s) matching condition: " +
+                "(has click action).filter(contains text 'word' " +
+                "(ignoreCase: 'false') as substring), " +
+                "but found '0'"
+        )
+    }
+
+    @Test
+    fun multipleFilters() {
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "some text"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another substring"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "yet another substring text"
+                    modifier = ActionModifier(LambdaAction("3") {})
+                })
+            }
+        )
+
+        assertion
+            .filter(hasText("text"))
+            .assertCountEquals(2)
+            .filter(hasText("substring"))
+            .assertCountEquals(1)
+    }
+
+    @Test
+    fun getIndexOnFilter() {
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "some text"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another substring"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "yet another substring text"
+                    modifier = ActionModifier(LambdaAction("3") {})
+                })
+            }
+        )
+
+        assertion
+            .filter(hasText("text"))
+            .assertCountEquals(2)
+            .get(index = 1)
+            .assert(hasTextEqualTo("yet another substring text"))
+    }
+
+    @Test
+    fun collectionGetIndex_notEnoughNodes_assertionError() {
+        val assertion = getGlanceNodeAssertionCollectionFor(
+            onAllNodesMatcher = hasClickAction(),
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "some text"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another substring"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "yet another substring text"
+                    modifier = ActionModifier(LambdaAction("3") {})
+                })
+            }
+        )
+
+        val assertionError = assertThrows(AssertionError::class.java) {
+            assertion
+                .filter(hasText("text"))
+                .assertCountEquals(2)
+                .get(index = 3) // index out of bounds.
+                .assert(hasTextEqualTo("yet another substring text"))
+        }
+
+        assertThat(assertionError).hasMessageThat().contains(
+            "Failed to assert condition: " +
+                "(text == 'yet another substring text' (ignoreCase: 'false'))" +
+                "\nReason: Not enough node(s) matching condition: " +
+                "((has click action).filter(contains text 'text' " +
+                "(ignoreCase: 'false') as substring)) " +
+                "to get node at index '4'. Found '2' matching node(s)"
+        )
+    }
+
+    @Test
+    fun assertOnChildren_multipleChildren() {
+        val assertion = getGlanceNodeAssertionFor(
+            onNodeMatcher = hasTestTag("test-list"),
+            emittable = EmittableColumn().apply {
+                modifier = GlanceModifier.semantics { testTag = "test-list" }
+                children.add(EmittableText().apply {
+                    text = "some text"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableText().apply {
+                    text = "another substring"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+            }
+        )
+
+        assertion
+            .onChildren()
+            .assertCountEquals(2)
+            .assertAll(hasClickAction())
+    }
+
+    @Test
+    fun assertOnChildren_noChildren() {
+        val assertion = getGlanceNodeAssertionFor(
+            onNodeMatcher = hasTestTag("test-list"),
+            emittable = EmittableColumn().apply {
+                modifier = GlanceModifier.semantics { testTag = "test-list" }
+            }
+        )
+
+        assertion.onChildren().assertCountEquals(0)
+    }
+
+    @Test
+    fun assertAnyOnChildren_noChildren_assertionError() {
+        val assertion = getGlanceNodeAssertionFor(
+            onNodeMatcher = hasTestTag("test-list"),
+            emittable = EmittableColumn().apply {
+                modifier = GlanceModifier.semantics { testTag = "test-list" }
+            }
+        )
+
+        val assertionError = assertThrows(AssertionError::class.java) {
+            assertion.onChildren().assertCountEquals(1)
+        }
+
+        assertThat(assertionError).hasMessageThat().contains(
+            "Failed to assert count of nodes" +
+                "\nReason: Expected '1' node(s) matching condition: " +
+                "(TestTag = 'test-list').children(), but found '0'"
+        )
+    }
+
+    @Test
+    fun filterOnChildren() {
+        val assertion = getGlanceNodeAssertionFor(
+            onNodeMatcher = hasTestTag("test-list"),
+            emittable = EmittableColumn().apply {
+                modifier = GlanceModifier.semantics { testTag = "test-list" }
+                children.add(EmittableText().apply {
+                    text = "some text"
+                    modifier = ActionModifier(LambdaAction("1") {})
+                })
+                children.add(EmittableText().apply {
+                    text = "another substring"
+                    modifier = ActionModifier(LambdaAction("2") {})
+                })
+            }
+        )
+
+        assertion
+            .onChildren()
+            .filter(hasText("substring"))
+            .assertCountEquals(1)
+    }
+
+    @Test
+    fun getIndexOnChildren() {
+        val assertion = getGlanceNodeAssertionFor(
+            onNodeMatcher = hasTestTag("test-list"),
+            emittable = EmittableColumn().apply {
+                modifier = GlanceModifier.semantics { testTag = "test-list" }
+                children.add(EmittableText().apply { text = "text-1" })
+                children.add(EmittableText().apply { text = "text-2" })
+            }
+        )
+
+        assertion
+            .onChildren()
+            .get(index = 0)
+            .assertHasText("text-1")
+    }
+}
diff --git a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/GlanceNodeAssertionTest.kt b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/GlanceNodeAssertionTest.kt
index c27c769..6718627 100644
--- a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/GlanceNodeAssertionTest.kt
+++ b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/GlanceNodeAssertionTest.kt
@@ -21,8 +21,7 @@
 import androidx.glance.layout.EmittableSpacer
 import androidx.glance.semantics.semantics
 import androidx.glance.semantics.testTag
-import androidx.glance.testing.unit.GlanceMappedNode
-import androidx.glance.testing.unit.MappedNode
+import androidx.glance.testing.unit.getGlanceNodeAssertionFor
 import androidx.glance.testing.unit.hasTestTag
 import androidx.glance.testing.unit.hasText
 import androidx.glance.text.EmittableText
@@ -35,22 +34,17 @@
 class GlanceNodeAssertionTest {
     @Test
     fun assertExists_success() {
-        val testContext = TestContext<MappedNode, GlanceMappedNode>()
-        // set root node of test tree to be traversed
-        testContext.rootGlanceNode = GlanceMappedNode(
-            EmittableColumn().apply {
+        // This is the object that in real usage a rule.onNode(matcher) would return.
+        val assertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
                 children.add(EmittableText().apply { text = "some text" })
                 children.add(EmittableSpacer())
                 children.add(EmittableText().apply {
                     text = "another text"
                     modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
                 })
-            }
-        )
-        // This is the object that in real usage a rule.onNode(matcher) would return.
-        val assertion = GlanceNodeAssertion(
-            matcher = hasTestTag(testTag = "existing-test-tag"),
-            testContext = testContext
+            },
+            onNodeMatcher = hasTestTag(testTag = "existing-test-tag"),
         )
 
         assertion.assertExists()
@@ -59,23 +53,16 @@
 
     @Test
     fun assertExists_error() {
-        val testContext = TestContext<MappedNode, GlanceMappedNode>()
-        // set root node of test tree to be traversed
-        testContext.rootGlanceNode =
-            GlanceMappedNode(
-                EmittableColumn().apply {
-                    children.add(EmittableText().apply { text = "some text" })
-                    children.add(EmittableSpacer())
-                    children.add(EmittableText().apply {
-                        text = "another text"
-                        modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
-                    })
-                }
-            )
-        // This is the object that in real usage a rule.onNode(matcher) would return.
-        val assertion = GlanceNodeAssertion(
-            matcher = hasTestTag(testTag = "non-existing-test-tag"),
-            testContext = testContext
+        val assertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply { text = "some text" })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another text"
+                    modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+                })
+            },
+            onNodeMatcher = hasTestTag(testTag = "non-existing-test-tag")
         )
 
         val assertionError = assertThrows(AssertionError::class.java) {
@@ -91,24 +78,16 @@
 
     @Test
     fun assertDoesNotExist_success() {
-        val testContext = TestContext<MappedNode, GlanceMappedNode>()
-        // set root node of test tree to be traversed
-        testContext.rootGlanceNode =
-            GlanceMappedNode(
-                EmittableColumn().apply {
-                    children.add(EmittableText().apply { text = "some text" })
-                    children.add(EmittableSpacer())
-                    children.add(EmittableText().apply {
-                        text = "another text"
-                        modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
-                    })
-                }
-            )
-
-        // This is the object that in real usage a rule.onNode(matcher) would return.
-        val assertion = GlanceNodeAssertion(
-            matcher = hasTestTag(testTag = "non-existing-test-tag"),
-            testContext = testContext
+        val assertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply { text = "some text" })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another text"
+                    modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+                })
+            },
+            onNodeMatcher = hasTestTag(testTag = "non-existing-test-tag")
         )
 
         assertion.assertDoesNotExist()
@@ -117,23 +96,16 @@
 
     @Test
     fun assertDoesNotExist_error() {
-        val testContext = TestContext<MappedNode, GlanceMappedNode>()
-        // set root node of test tree to be traversed
-        testContext.rootGlanceNode =
-            GlanceMappedNode(
-                EmittableColumn().apply {
-                    children.add(EmittableText().apply { text = "some text" })
-                    children.add(EmittableSpacer())
-                    children.add(EmittableText().apply {
-                        text = "another text"
-                        modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
-                    })
-                }
-            )
-        // This is the object that in real usage a rule.onNode(matcher) would return.
-        val assertion = GlanceNodeAssertion(
-            matcher = hasTestTag(testTag = "existing-test-tag"),
-            testContext = testContext
+        val assertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply { text = "some text" })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another text"
+                    modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+                })
+            },
+            onNodeMatcher = hasTestTag(testTag = "existing-test-tag")
         )
 
         val assertionError = assertThrows(AssertionError::class.java) {
@@ -149,23 +121,16 @@
 
     @Test
     fun assert_withMatcher_success() {
-        val testContext = TestContext<MappedNode, GlanceMappedNode>()
-        // set root node of test tree to be traversed
-        testContext.rootGlanceNode =
-            GlanceMappedNode(
-                EmittableColumn().apply {
-                    children.add(EmittableText().apply { text = "some text" })
-                    children.add(EmittableSpacer())
-                    children.add(EmittableText().apply {
-                        text = "another text"
-                        modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
-                    })
-                }
-            )
-        // This is the object that in real usage a rule.onNode(matcher) would return.
-        val assertion = GlanceNodeAssertion(
-            matcher = hasTestTag(testTag = "existing-test-tag"),
-            testContext = testContext
+        val assertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply { text = "some text" })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another text"
+                    modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+                })
+            },
+            onNodeMatcher = hasTestTag(testTag = "existing-test-tag")
         )
 
         assertion.assert(hasText(text = "another text"))
@@ -174,24 +139,16 @@
 
     @Test
     fun chainAssertions() {
-        val testContext = TestContext<MappedNode, GlanceMappedNode>()
-        // set root node of test tree to be traversed
-        testContext.rootGlanceNode =
-            GlanceMappedNode(
-                MappedNode(
-                    EmittableColumn().apply {
-                        children.add(EmittableText().apply { text = "some text" })
-                        children.add(EmittableSpacer())
-                        children.add(EmittableText().apply {
-                            text = "another text"
-                            modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
-                        })
-                    })
-            )
-        // This is the object that in real usage a rule.onNode(matcher) would return.
-        val assertion = GlanceNodeAssertion(
-            matcher = hasTestTag(testTag = "existing-test-tag"),
-            testContext = testContext
+        val assertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply { text = "some text" })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another text"
+                    modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+                })
+            },
+            onNodeMatcher = hasTestTag(testTag = "existing-test-tag")
         )
 
         assertion
@@ -202,25 +159,16 @@
 
     @Test
     fun chainAssertion_failureInFirst() {
-        val testContext = TestContext<MappedNode, GlanceMappedNode>()
-        // set root node of test tree to be traversed
-        testContext.rootGlanceNode =
-            GlanceMappedNode(
-                MappedNode(
-                    EmittableColumn().apply {
-                        children.add(EmittableText().apply { text = "some text" })
-                        children.add(EmittableSpacer())
-                        children.add(EmittableText().apply {
-                            text = "another text"
-                            modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
-                        })
-                    }
-                )
-            )
-        // This is the object that in real usage a rule.onNode(matcher) would return.
-        val assertion = GlanceNodeAssertion(
-            matcher = hasTestTag(testTag = "existing-test-tag"),
-            testContext = testContext
+        val assertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply { text = "some text" })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another text"
+                    modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+                })
+            },
+            onNodeMatcher = hasTestTag(testTag = "existing-test-tag")
         )
 
         val assertionError = assertThrows(AssertionError::class.java) {
@@ -240,25 +188,16 @@
 
     @Test
     fun chainAssertion_failureInSecond() {
-        val testContext = TestContext<MappedNode, GlanceMappedNode>()
-        // set root node of test tree to be traversed
-        testContext.rootGlanceNode =
-            GlanceMappedNode(
-                MappedNode(
-                    EmittableColumn().apply {
-                        children.add(EmittableText().apply { text = "some text" })
-                        children.add(EmittableSpacer())
-                        children.add(EmittableText().apply {
-                            text = "another text"
-                            modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
-                        })
-                    }
-                )
-            )
-        // This is the object that in real usage a rule.onNode(matcher) would return.
-        val assertion = GlanceNodeAssertion(
-            matcher = hasTestTag(testTag = "existing-test-tag"),
-            testContext = testContext
+        val assertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply { text = "some text" })
+                children.add(EmittableSpacer())
+                children.add(EmittableText().apply {
+                    text = "another text"
+                    modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+                })
+            },
+            onNodeMatcher = hasTestTag(testTag = "existing-test-tag")
         )
 
         val assertionError = assertThrows(AssertionError::class.java) {
@@ -271,7 +210,7 @@
             .hasMessageThat()
             .startsWith(
                 "Failed to assert condition: " +
-                    "(has text = 'non-existing text' (ignoreCase: 'false'))" +
+                    "(contains text 'non-existing text' (ignoreCase: 'false') as substring)" +
                     "\nGlance Node:"
             )
     }
diff --git a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/GlanceMappedNodeMatcherTest.kt b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/GlanceMappedNodeMatcherTest.kt
new file mode 100644
index 0000000..880393f
--- /dev/null
+++ b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/GlanceMappedNodeMatcherTest.kt
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.glance.testing.unit
+
+import androidx.glance.GlanceModifier
+import androidx.glance.layout.EmittableColumn
+import androidx.glance.semantics.semantics
+import androidx.glance.semantics.testTag
+import androidx.glance.text.EmittableText
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class GlanceMappedNodeMatcherTest {
+    @Test
+    fun matchAny_match_returnsTrue() {
+        val node1 = GlanceMappedNode(
+            EmittableText().apply {
+                text = "node1"
+            }
+        )
+        val node2 = GlanceMappedNode(
+            EmittableColumn().apply {
+                modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+                EmittableText().apply { text = "node2" }
+            }
+        )
+
+        val result = hasTestTag("existing-test-tag").matchesAny(listOf(node1, node2))
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun matchAny_noMatch_returnsFalse() {
+        val node1 = GlanceMappedNode(
+            EmittableText().apply {
+                text = "node1"
+            }
+        )
+        val node2 = GlanceMappedNode(
+            EmittableColumn().apply {
+                children += EmittableText().apply {
+                    text = "node2"
+                    // this won't be inspected, as EmittableColumn node is being run against
+                    // matcher, not its children
+                    modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+                }
+            }
+        )
+
+        val result = hasTestTag("existing-test-tag").matchesAny(listOf(node1, node2))
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun andBetweenMatchers_match_returnsTrue() {
+        val node = GlanceMappedNode(
+            EmittableText().apply {
+                text = "node"
+                modifier = GlanceModifier.semantics { testTag = "test-tag" }
+            }
+        )
+
+        val result = (hasTestTag("test-tag") and hasText("node")).matches(node)
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun andBetweenMatchers_partialMatch_returnsFalse() {
+        val node = GlanceMappedNode(
+            EmittableText().apply {
+                text = "node"
+                modifier = GlanceModifier.semantics { testTag = "test-tag" }
+            }
+        )
+
+        val result = (hasTestTag("test-tag") and hasText("non-existing-node"))
+            .matches(node)
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun andBetweenMatchers_noMatch_returnsFalse() {
+        val node = GlanceMappedNode(
+            EmittableText().apply {
+                text = "node"
+                modifier = GlanceModifier.semantics { testTag = "test-tag" }
+            }
+        )
+
+        val result = (hasTestTag("non-existing") and hasText("non-existing-node"))
+            .matches(node)
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun orBetweenMatchers_bothMatch_returnsTrue() {
+        val node = GlanceMappedNode(
+            EmittableText().apply {
+                text = "node"
+                modifier = GlanceModifier.semantics { testTag = "test-tag" }
+            }
+        )
+
+        val result = (hasTestTag("test-tag") or hasText("node"))
+            .matches(node)
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun orBetweenMatchers_secondMatch_returnsTrue() {
+        val node = GlanceMappedNode(
+            EmittableText().apply {
+                text = "node"
+                modifier = GlanceModifier.semantics { testTag = "test-tag" }
+            }
+        )
+
+        val result = (hasTestTag("non-existing-tag") or hasText("node"))
+            .matches(node)
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun orBetweenMatchers_noneMatch_returnsFalse() {
+        val node = GlanceMappedNode(
+            EmittableText().apply {
+                text = "node"
+                modifier = GlanceModifier.semantics { testTag = "test-tag" }
+            }
+        )
+
+        val result = (hasTestTag("non-existing-tag") or hasText("non-existing-node"))
+            .matches(node)
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun not_match_returnsTrue() {
+        val node = GlanceMappedNode(
+            EmittableText().apply {
+                text = "node"
+                modifier = GlanceModifier.semantics { testTag = "test-tag" }
+            }
+        )
+
+        val result = (!hasTestTag("non-existing-test-tag")).matches(node)
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun not_noMatch_returnsFalse() {
+        val node = GlanceMappedNode(
+            EmittableText().apply {
+                text = "node"
+                modifier = GlanceModifier.semantics { testTag = "test-tag" }
+            }
+        )
+
+        val result = (!hasTestTag("test-tag")).matches(node)
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun hasTestTag_match_returnsTrue() {
+        // a single node that will be matched against matcher returned by the filter under test
+        val testSingleNode = GlanceMappedNode(
+            EmittableText().apply {
+                text = "some text"
+                modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+            }
+        )
+
+        val result = hasTestTag("existing-test-tag").matches(testSingleNode)
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun hasTestTag_noMatch_returnsFalse() {
+        val testSingleNode = GlanceMappedNode(
+            EmittableText().apply {
+                text = "some text"
+                modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
+            }
+        )
+
+        val result = hasTestTag("non-existing-test-tag").matches(testSingleNode)
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun hasTextEqualTo_match_returnsTrue() {
+        val testSingleNode = GlanceMappedNode(
+            EmittableText().apply {
+                text = "existing text"
+            }
+        )
+
+        val result = hasTextEqualTo("existing text").matches(testSingleNode)
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun hasTextEqualTo_noMatch_returnsFalse() {
+        val testSingleNode = GlanceMappedNode(
+            EmittableText().apply {
+                text = "existing text"
+            }
+        )
+
+        val result = hasTextEqualTo("non-existing text").matches(testSingleNode)
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun hasTextEqualTo_caseInsensitiveMatch_returnsTrue() {
+        val testSingleNode = GlanceMappedNode(
+            EmittableText().apply {
+                text = "some EXISTING text"
+            }
+        )
+
+        val result =
+            hasTextEqualTo(
+                text = "SOME existing TEXT",
+                ignoreCase = true
+            ).matches(testSingleNode)
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun hasTextEqualTo_caseInsensitiveButNoMatch_returnsFalse() {
+        val testSingleNode = GlanceMappedNode(
+            EmittableText().apply {
+                text = "some EXISTING text"
+            }
+        )
+
+        val result =
+            hasTextEqualTo(
+                text = "SOME non-existing TEXT",
+                ignoreCase = true
+            ).matches(testSingleNode)
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun hasText_match_returnsTrue() {
+        val testSingleNode = GlanceMappedNode(
+            EmittableText().apply {
+                text = "some existing text"
+            }
+        )
+
+        val result = hasText("existing").matches(testSingleNode)
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun hasText_noMatch_returnsFalse() {
+        val testSingleNode = GlanceMappedNode(
+            EmittableText().apply {
+                text = "some existing text"
+            }
+        )
+
+        val result = hasText("non-existing").matches(testSingleNode)
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun hasText_insensitiveMatch_returnsTrue() {
+        val testSingleNode = GlanceMappedNode(
+            EmittableText().apply {
+                text = "some EXISTING text"
+            }
+        )
+
+        val result = hasText(
+            text = "existing",
+            ignoreCase = true
+        ).matches(testSingleNode)
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun hasText_caseInsensitiveButNoMatch_returnsFalse() {
+        val testSingleNode = GlanceMappedNode(
+            EmittableText().apply {
+                text = "some EXISTING text"
+            }
+        )
+
+        val result = hasText(
+            text = "non-EXISTING",
+            ignoreCase = true
+        ).matches(testSingleNode)
+
+        assertThat(result).isFalse()
+    }
+}
diff --git a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/UnitTestAssertionExtensionsTest.kt b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/UnitTestAssertionExtensionsTest.kt
index b404ec7..d4800b2 100644
--- a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/UnitTestAssertionExtensionsTest.kt
+++ b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/UnitTestAssertionExtensionsTest.kt
@@ -30,6 +30,162 @@
 // and relevant to unit tests
 class UnitTestAssertionExtensionsTest {
     @Test
+    fun assertHasTextEqualTo_matching() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "test text"
+                    modifier = GlanceModifier.semantics { testTag = "test-tag" }
+                })
+            },
+            onNodeMatcher = hasTestTag("test-tag")
+        )
+
+        nodeAssertion.assertHasTextEqualTo("test text")
+    }
+
+    @Test
+    fun assertHasTextEqualTo_ignoreCase_matching() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "test text"
+                    modifier = GlanceModifier.semantics { testTag = "test-tag" }
+                })
+            },
+            onNodeMatcher = hasTestTag("test-tag")
+        )
+
+        nodeAssertion.assertHasTextEqualTo(text = "TEST TEXT", ignoreCase = true)
+    }
+
+    @Test
+    fun assertHasTextEqualTo_notMatching_assertionError() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "test text"
+                    modifier = GlanceModifier.semantics { testTag = "test-tag" }
+                })
+            },
+            onNodeMatcher = hasTestTag("test-tag")
+        )
+
+        val assertionError = assertThrows(AssertionError::class.java) {
+            nodeAssertion.assertHasTextEqualTo("non-existing text")
+        }
+
+        assertThat(assertionError)
+            .hasMessageThat()
+            .contains(
+                "Failed to assert condition: " +
+                    "(text == 'non-existing text' (ignoreCase: 'false'))"
+            )
+    }
+
+    @Test
+    fun assertHasTextEqualTo_ignoreCaseAndNotMatching_assertionError() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "test text"
+                    modifier = GlanceModifier.semantics { testTag = "test-tag" }
+                })
+            },
+            onNodeMatcher = hasTestTag("test-tag")
+        )
+
+        val assertionError = assertThrows(AssertionError::class.java) {
+            nodeAssertion.assertHasTextEqualTo("NON-EXISTING TEXT", ignoreCase = true)
+        }
+
+        assertThat(assertionError)
+            .hasMessageThat()
+            .contains(
+                "Failed to assert condition: " +
+                    "(text == 'NON-EXISTING TEXT' (ignoreCase: 'true'))"
+            )
+    }
+
+    @Test
+    fun assertHasText_matching() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "test text"
+                    modifier = GlanceModifier.semantics { testTag = "test-tag" }
+                })
+            },
+            onNodeMatcher = hasTestTag("test-tag")
+        )
+
+        nodeAssertion.assertHasText(text = "text")
+    }
+
+    @Test
+    fun assertHasText_ignoreCase_matching() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "test text"
+                    modifier = GlanceModifier.semantics { testTag = "test-tag" }
+                })
+            },
+            onNodeMatcher = hasTestTag("test-tag")
+        )
+
+        nodeAssertion.assertHasText(text = "TEXT", ignoreCase = true)
+    }
+
+    @Test
+    fun assertHasText_notMatching_assertionError() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "test text"
+                    modifier = GlanceModifier.semantics { testTag = "test-tag" }
+                })
+            },
+            onNodeMatcher = hasTestTag("test-tag")
+        )
+
+        val assertionError = assertThrows(AssertionError::class.java) {
+            nodeAssertion.assertHasText("non-existing")
+        }
+
+        assertThat(assertionError)
+            .hasMessageThat()
+            .contains(
+                "Failed to assert condition: " +
+                    "(contains text 'non-existing' (ignoreCase: 'false') as substring)"
+            )
+    }
+
+    @Test
+    fun assertHasText_ignoreCaseAndNotMatching_assertionError() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "test text"
+                    modifier = GlanceModifier.semantics { testTag = "test-tag" }
+                })
+            },
+            onNodeMatcher = hasTestTag("test-tag")
+        )
+
+        val assertionError = assertThrows(AssertionError::class.java) {
+            nodeAssertion.assertHasText(text = "NON-EXISTING", ignoreCase = true)
+        }
+
+        assertThat(assertionError)
+            .hasMessageThat()
+            .contains(
+                "Failed to assert condition: " +
+                    "(contains text 'NON-EXISTING' (ignoreCase: 'true') as substring)"
+            )
+    }
+
+    @Test
     fun assertHasTestTag_matching() {
         val nodeAssertion = getGlanceNodeAssertionFor(
             emittable = EmittableColumn().apply {
@@ -66,6 +222,99 @@
     }
 
     @Test
+    fun assertHasContentDescriptionEqualTo_matching() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "test text"
+                    modifier = GlanceModifier.semantics {
+                        testTag = "test-tag"
+                        contentDescription = "test text description"
+                    }
+                })
+            },
+            onNodeMatcher = hasTestTag("test-tag")
+        )
+
+        nodeAssertion.assertHasContentDescriptionEqualTo("test text description")
+    }
+
+    @Test
+    fun assertHasContentDescriptionEqualTo_ignoreCaseMatching() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "test text"
+                    modifier = GlanceModifier.semantics {
+                        testTag = "test-tag"
+                        contentDescription = "test text description"
+                    }
+                })
+            },
+            onNodeMatcher = hasTestTag("test-tag")
+        )
+
+        nodeAssertion.assertHasContentDescriptionEqualTo(
+            value = "TEST TEXT DESCRIPTION",
+            ignoreCase = true
+        )
+    }
+
+    @Test
+    fun assertHasContentDescriptionEqualTo_notMatching_assertionError() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "test text"
+                    modifier = GlanceModifier.semantics { testTag = "test-tag" }
+                })
+            },
+            onNodeMatcher = hasTestTag("test-tag")
+        )
+
+        val assertionError = assertThrows(AssertionError::class.java) {
+            nodeAssertion.assertHasContentDescriptionEqualTo("text description")
+        }
+
+        assertThat(assertionError)
+            .hasMessageThat()
+            .contains(
+                "Failed to assert condition: " +
+                    "(ContentDescription == 'text description' (ignoreCase: 'false'))"
+            )
+    }
+
+    @Test
+    fun assertHasContentDescriptionEqualTo_ignoreCaseAndNotMatching_assertionError() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "test text"
+                    modifier = GlanceModifier.semantics {
+                        testTag = "test-tag"
+                        contentDescription = "test text description"
+                    }
+                })
+            },
+            onNodeMatcher = hasTestTag("test-tag")
+        )
+
+        val assertionError = assertThrows(AssertionError::class.java) {
+            nodeAssertion.assertHasContentDescriptionEqualTo(
+                value = "TEST DESCRIPTION",
+                ignoreCase = true
+            )
+        }
+
+        assertThat(assertionError)
+            .hasMessageThat()
+            .contains(
+                "Failed to assert condition: " +
+                    "(ContentDescription == 'TEST DESCRIPTION' (ignoreCase: 'true'))"
+            )
+    }
+
+    @Test
     fun assertHasContentDescription_matching() {
         val nodeAssertion = getGlanceNodeAssertionFor(
             emittable = EmittableColumn().apply {
@@ -80,7 +329,28 @@
             onNodeMatcher = hasTestTag("test-tag")
         )
 
-        nodeAssertion.assertHasContentDescription("test text description")
+        nodeAssertion.assertHasContentDescription("text")
+    }
+
+    @Test
+    fun assertHasContentDescription_ignoreCaseMatching() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "test text"
+                    modifier = GlanceModifier.semantics {
+                        testTag = "test-tag"
+                        contentDescription = "test text description"
+                    }
+                })
+            },
+            onNodeMatcher = hasTestTag("test-tag")
+        )
+
+        nodeAssertion.assertHasContentDescription(
+            value = "TEXT",
+            ignoreCase = true
+        )
     }
 
     @Test
@@ -103,7 +373,39 @@
             .hasMessageThat()
             .contains(
                 "Failed to assert condition: " +
-                    "(ContentDescription = 'test text description' (ignoreCase: 'false'))"
+                    "(ContentDescription contains 'test text description' " +
+                    "(ignoreCase: 'false') as substring)"
+            )
+    }
+
+    @Test
+    fun assertHasContentDescription_ignoreCaseAndNotMatching_assertionError() {
+        val nodeAssertion = getGlanceNodeAssertionFor(
+            emittable = EmittableColumn().apply {
+                children.add(EmittableText().apply {
+                    text = "test text"
+                    modifier = GlanceModifier.semantics {
+                        testTag = "test-tag"
+                        contentDescription = "text"
+                    }
+                })
+            },
+            onNodeMatcher = hasTestTag("test-tag")
+        )
+
+        val assertionError = assertThrows(AssertionError::class.java) {
+            nodeAssertion.assertHasContentDescription(
+                value = "TEXT DESCRIPTION",
+                ignoreCase = true
+            )
+        }
+
+        assertThat(assertionError)
+            .hasMessageThat()
+            .contains(
+                "Failed to assert condition: " +
+                    "(ContentDescription contains 'TEXT DESCRIPTION' (ignoreCase: 'true') " +
+                    "as substring)"
             )
     }
 }
diff --git a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/GlanceMappedNodeFiltersAndMatcherTest.kt b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/UnitTestFiltersTest.kt
similarity index 62%
rename from glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/GlanceMappedNodeFiltersAndMatcherTest.kt
rename to glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/UnitTestFiltersTest.kt
index 2fb3c52..69aa1a9 100644
--- a/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/GlanceMappedNodeFiltersAndMatcherTest.kt
+++ b/glance/glance-testing/src/test/kotlin/androidx/glance/testing/unit/UnitTestFiltersTest.kt
@@ -24,49 +24,7 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 
-class GlanceMappedNodeFiltersAndMatcherTest {
-    @Test
-    fun matchAny_match_returnsTrue() {
-        val node1 = GlanceMappedNode(
-            EmittableText().apply {
-                text = "node1"
-            }
-        )
-        val node2 = GlanceMappedNode(
-            EmittableColumn().apply {
-                modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
-                EmittableText().apply { text = "node2" }
-            }
-        )
-
-        val result = hasTestTag("existing-test-tag").matchesAny(listOf(node1, node2))
-
-        assertThat(result).isTrue()
-    }
-
-    @Test
-    fun matchAny_noMatch_returnsFalse() {
-        val node1 = GlanceMappedNode(
-            EmittableText().apply {
-                text = "node1"
-            }
-        )
-        val node2 = GlanceMappedNode(
-            EmittableColumn().apply {
-                EmittableText().apply {
-                    text = "node2"
-                    // this won't be inspected, as EmittableColumn node is being run against
-                    // matcher, not its children
-                    modifier = GlanceModifier.semantics { testTag = "existing-test-tag" }
-                }
-            }
-        )
-
-        val result = hasTestTag("existing-test-tag").matchesAny(listOf(node1, node2))
-
-        assertThat(result).isFalse()
-    }
-
+class UnitTestFiltersTest {
     @Test
     fun hasTestTag_match_returnsTrue() {
         // a single node that will be matched against matcher returned by the filter under test
@@ -97,14 +55,74 @@
     }
 
     @Test
-    fun hasText_match_returnsTrue() {
+    fun hasTextEqualTo_match_returnsTrue() {
         val testSingleNode = GlanceMappedNode(
             EmittableText().apply {
                 text = "existing text"
             }
         )
 
-        val result = hasText("existing text").matches(testSingleNode)
+        val result = hasTextEqualTo("existing text").matches(testSingleNode)
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun hasTextEqualTo_noMatch_returnsFalse() {
+        val testSingleNode = GlanceMappedNode(
+            EmittableText().apply {
+                text = "existing text"
+            }
+        )
+
+        val result = hasTextEqualTo("non-existing text").matches(testSingleNode)
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun hasTextEqualTo_caseInsensitiveMatch_returnsTrue() {
+        val testSingleNode = GlanceMappedNode(
+            EmittableText().apply {
+                text = "some EXISTING text"
+            }
+        )
+
+        val result =
+            hasTextEqualTo(
+                text = "SOME existing TEXT",
+                ignoreCase = true
+            ).matches(testSingleNode)
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun hasTextEqualTo_caseInsensitiveButNoMatch_returnsFalse() {
+        val testSingleNode = GlanceMappedNode(
+            EmittableText().apply {
+                text = "some EXISTING text"
+            }
+        )
+
+        val result =
+            hasTextEqualTo(
+                text = "SOME non-existing TEXT",
+                ignoreCase = true
+            ).matches(testSingleNode)
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun hasText_match_returnsTrue() {
+        val testSingleNode = GlanceMappedNode(
+            EmittableText().apply {
+                text = "some existing text"
+            }
+        )
+
+        val result = hasText("existing").matches(testSingleNode)
 
         assertThat(result).isTrue()
     }
@@ -113,94 +131,91 @@
     fun hasText_noMatch_returnsFalse() {
         val testSingleNode = GlanceMappedNode(
             EmittableText().apply {
-                text = "existing text"
-            }
-        )
-
-        val result = hasText("non-existing text").matches(testSingleNode)
-
-        assertThat(result).isFalse()
-    }
-
-    @Test
-    fun hasText_subStringMatch_returnsTrue() {
-        val testSingleNode = GlanceMappedNode(
-            EmittableText().apply {
                 text = "some existing text"
             }
         )
 
-        val result = hasText(text = "existing", substring = true).matches(testSingleNode)
-
-        assertThat(result).isTrue()
-    }
-
-    @Test
-    fun hasText_subStringNoMatch_returnsFalse() {
-        val testSingleNode = GlanceMappedNode(
-            EmittableText().apply {
-                text = "some existing text"
-            }
-        )
-
-        val result = hasText(text = "non-existing", substring = true).matches(testSingleNode)
+        val result = hasText("non-existing").matches(testSingleNode)
 
         assertThat(result).isFalse()
     }
 
     @Test
-    fun hasText_subStringCaseInsensitiveMatch_returnsTrue() {
+    fun hasText_insensitiveMatch_returnsTrue() {
         val testSingleNode = GlanceMappedNode(
             EmittableText().apply {
                 text = "some EXISTING text"
             }
         )
 
-        val result =
-            hasText(text = "existing", substring = true, ignoreCase = true).matches(testSingleNode)
+        val result = hasText(
+            text = "existing",
+            ignoreCase = true
+        ).matches(testSingleNode)
 
         assertThat(result).isTrue()
     }
 
     @Test
-    fun hasText_subStringCaseInsensitiveNoMatch_returnsFalse() {
+    fun hasText_caseInsensitiveButNoMatch_returnsFalse() {
         val testSingleNode = GlanceMappedNode(
             EmittableText().apply {
                 text = "some EXISTING text"
             }
         )
 
-        val result =
-            hasText(text = "non-EXISTING", substring = true, ignoreCase = true)
-                .matches(testSingleNode)
+        val result = hasText(
+            text = "non-EXISTING",
+            ignoreCase = true
+        ).matches(testSingleNode)
 
         assertThat(result).isFalse()
     }
 
     @Test
-    fun hasText_caseInsensitiveMatch_returnsTrue() {
-        val testSingleNode = GlanceMappedNode(
-            EmittableText().apply {
-                text = "some EXISTING text"
+    fun hasAnyDescendant_match_returnsTrue() {
+        val testNode = GlanceMappedNode(
+            EmittableColumn().apply {
+                children += EmittableText().apply {
+                    text = "node1"
+                }
+                children += EmittableColumn().apply {
+                    children += EmittableText().apply {
+                        text = "node2-a"
+                    }
+                    children += EmittableText().apply {
+                        text = "node2-b"
+                    }
+                }
             }
         )
 
         val result =
-            hasText(text = "SOME existing TEXT", ignoreCase = true).matches(testSingleNode)
+            hasAnyDescendant(hasText("node2-b")).matches(testNode)
 
         assertThat(result).isTrue()
     }
 
     @Test
-    fun hasText_caseInsensitiveNoMatch_returnsFalse() {
-        val testSingleNode = GlanceMappedNode(
-            EmittableText().apply {
-                text = "some EXISTING text"
+    fun hasAnyDescendant_noMatch_returnsFalse() {
+        val testNode = GlanceMappedNode(
+            EmittableColumn().apply {
+                children += EmittableText().apply {
+                    text = "node1"
+                }
+                children += EmittableColumn().apply {
+                    children += EmittableText().apply {
+                        text = "node2-a"
+                    }
+                    children += EmittableText().apply {
+                        text = "node2-b"
+                    }
+                }
             }
         )
 
         val result =
-            hasText(text = "SOME non-existing TEXT", ignoreCase = true).matches(testSingleNode)
+            hasAnyDescendant(hasText("node3-a")).matches(testNode)
 
         assertThat(result).isFalse()
     }
diff --git a/glance/glance-wear-tiles-preview/lint-baseline.xml b/glance/glance-wear-tiles-preview/lint-baseline.xml
new file mode 100644
index 0000000..d2965f7
--- /dev/null
+++ b/glance/glance-wear-tiles-preview/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.2.0-alpha15" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-alpha15)" variant="all" version="8.2.0-alpha15">
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="                .all { it }"
+        errorLine2="                 ~~~">
+        <location
+            file="src/main/java/androidx/glance/wear/tiles/preview/ComposableInvoker.kt"/>
+    </issue>
+
+</issues>
diff --git a/glance/glance-wear-tiles-preview/src/main/java/androidx/glance/wear/tiles/preview/ComposableInvoker.kt b/glance/glance-wear-tiles-preview/src/main/java/androidx/glance/wear/tiles/preview/ComposableInvoker.kt
index dcfe376..2105f3c 100644
--- a/glance/glance-wear-tiles-preview/src/main/java/androidx/glance/wear/tiles/preview/ComposableInvoker.kt
+++ b/glance/glance-wear-tiles-preview/src/main/java/androidx/glance/wear/tiles/preview/ComposableInvoker.kt
@@ -33,7 +33,6 @@
      * Returns true if the [methodTypes] and [actualTypes] are compatible. This means that every
      * `actualTypes[n]` are assignable to `methodTypes[n]`.
      */
-    @Suppress("ListIterator")
     private fun compatibleTypes(
         methodTypes: Array<Class<*>>,
         actualTypes: Array<Class<*>>
diff --git a/glance/glance-wear-tiles/lint-baseline.xml b/glance/glance-wear-tiles/lint-baseline.xml
new file mode 100644
index 0000000..5a135eb
--- /dev/null
+++ b/glance/glance-wear-tiles/lint-baseline.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.2.0-alpha15" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-alpha15)" variant="all" version="8.2.0-alpha15">
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        curvedChildList.forEach { composable ->"
+        errorLine2="                        ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/wear/tiles/curved/CurvedRow.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        it.children.addAll(children.map { it.copy() })"
+        errorLine2="                                    ~~~">
+        <location
+            file="src/main/java/androidx/glance/wear/tiles/curved/CurvedRow.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        it.children.addAll(children.map { it.copy() })"
+        errorLine2="                                    ~~~">
+        <location
+            file="src/main/java/androidx/glance/wear/tiles/curved/CurvedRow.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    children.mapIndexed { index, child ->"
+        errorLine2="             ~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/wear/tiles/NormalizeCompositionTree.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    toDelete.forEach {"
+        errorLine2="             ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/wear/tiles/NormalizeCompositionTree.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            children.forEach { child ->"
+        errorLine2="                     ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/wear/tiles/NormalizeCompositionTree.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        textList.forEach { item ->"
+        errorLine2="                 ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/wear/tiles/template/SingleEntityTemplateLayouts.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            .setContentDescription(it.joinToString())"
+        errorLine2="                                      ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/wear/tiles/WearCompositionTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            .setContentDescription(it.joinToString())"
+        errorLine2="                                      ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/wear/tiles/WearCompositionTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            element.children.forEach {"
+        errorLine2="                             ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/wear/tiles/WearCompositionTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="                element.children.forEach {"
+        errorLine2="                                 ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/wear/tiles/WearCompositionTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="                element.children.forEach {"
+        errorLine2="                                 ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/wear/tiles/WearCompositionTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    element.children.forEach { curvedChild ->"
+        errorLine2="                     ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/wear/tiles/WearCompositionTranslator.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            curvedChild.children.forEach {"
+        errorLine2="                                 ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/wear/tiles/WearCompositionTranslator.kt"/>
+    </issue>
+
+</issues>
diff --git a/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/NormalizeCompositionTree.kt b/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/NormalizeCompositionTree.kt
index 8666f31..557632d 100644
--- a/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/NormalizeCompositionTree.kt
+++ b/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/NormalizeCompositionTree.kt
@@ -20,8 +20,6 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastMapIndexed
 import androidx.glance.AndroidResourceImageProvider
 import androidx.glance.BitmapImageProvider
 import androidx.glance.Emittable
@@ -55,7 +53,7 @@
 /** Transform each node in the tree. */
 private fun EmittableWithChildren.transformTree(block: (Emittable) -> Emittable?) {
     val toDelete = mutableListOf<Int>()
-    children.fastMapIndexed { index, child ->
+    children.mapIndexed { index, child ->
         val newChild = block(child)
         if (newChild == null) {
             toDelete += index
@@ -65,7 +63,7 @@
         if (newChild is EmittableWithChildren) newChild.transformTree(block)
     }
     toDelete.reverse()
-    toDelete.fastForEach {
+    toDelete.forEach {
         children.removeAt(it)
     }
 }
@@ -88,7 +86,7 @@
     return when (this) {
         is EmittableWithChildren -> {
             modifier = GlanceModifier.then(WidthModifier(width)).then(HeightModifier(height))
-            children.fastForEach { child ->
+            children.forEach { child ->
                 val visibility =
                     child.modifier.findModifier<VisibilityModifier>()?.visibility
                         ?: Visibility.Visible
diff --git a/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/WearCompositionTranslator.kt b/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/WearCompositionTranslator.kt
index 99565c4..72071c9 100644
--- a/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/WearCompositionTranslator.kt
+++ b/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/WearCompositionTranslator.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
+@file:Suppress("deprecation")
 package androidx.glance.wear.tiles
 
 import android.content.Context
@@ -23,8 +23,6 @@
 import android.view.ViewGroup
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastJoinToString
 import androidx.glance.AndroidResourceImageProvider
 import androidx.glance.BackgroundModifier
 import androidx.glance.BitmapImageProvider
@@ -78,6 +76,7 @@
 import androidx.glance.wear.tiles.curved.SweepAngleModifier
 import androidx.glance.wear.tiles.curved.ThicknessModifier
 import androidx.glance.wear.tiles.curved.findModifier
+import androidx.wear.tiles.ColorBuilders
 import java.io.ByteArrayOutputStream
 import java.util.Arrays
 
@@ -128,12 +127,13 @@
 @Suppress("deprecation") // for backward compatibility
 private fun BackgroundModifier.toProto(
     context: Context
-): androidx.wear.tiles.ModifiersBuilders.Background? =
-    this.colorProvider?.let { provider ->
+): androidx.wear.tiles.ModifiersBuilders.Background? = when (this) {
+    is BackgroundModifier.Color ->
         androidx.wear.tiles.ModifiersBuilders.Background.Builder()
-            .setColor(androidx.wear.tiles.ColorBuilders.argb(provider.getColorAsArgb(context)))
+            .setColor(ColorBuilders.argb(this.colorProvider.getColorAsArgb(context)))
             .build()
-    }
+    else -> error("Unexpected modifier $this")
+}
 
 @Suppress("deprecation") // for backward compatibility
 private fun BorderModifier.toProto(context: Context): androidx.wear.tiles.ModifiersBuilders.Border =
@@ -148,7 +148,7 @@
 private fun SemanticsModifier.toProto(): androidx.wear.tiles.ModifiersBuilders.Semantics? =
     this.configuration.getOrNull(SemanticsProperties.ContentDescription)?.let {
         androidx.wear.tiles.ModifiersBuilders.Semantics.Builder()
-            .setContentDescription(it.fastJoinToString())
+            .setContentDescription(it.joinToString())
             .build()
     }
 
@@ -156,7 +156,7 @@
 private fun SemanticsCurvedModifier.toProto(): androidx.wear.tiles.ModifiersBuilders.Semantics? =
     this.configuration.getOrNull(SemanticsProperties.ContentDescription)?.let {
         androidx.wear.tiles.ModifiersBuilders.Semantics.Builder()
-            .setContentDescription(it.fastJoinToString())
+            .setContentDescription(it.joinToString())
             .build()
     }
 
@@ -331,7 +331,7 @@
         .setWidth(element.modifier.getWidth(context).toContainerDimension())
         .setHeight(element.modifier.getHeight(context).toContainerDimension())
         .also { box ->
-            element.children.fastForEach {
+            element.children.forEach {
                 box.addContent(translateComposition(context, resourceBuilder, it))
             }
         }
@@ -351,7 +351,7 @@
             .setHeight(height.toContainerDimension())
             .setVerticalAlignment(element.verticalAlignment.toProto())
             .also { row ->
-                element.children.fastForEach {
+                element.children.forEach {
                     row.addContent(translateComposition(context, resourceBuilder, it))
                 }
             }
@@ -391,7 +391,7 @@
             .setWidth(width.toContainerDimension())
             .setHorizontalAlignment(element.horizontalAlignment.toProto())
             .also { column ->
-                element.children.fastForEach {
+                element.children.forEach {
                     column.addContent(translateComposition(context, resourceBuilder, it))
                 }
             }
@@ -637,9 +637,9 @@
             .setVerticalAlign(element.radialAlignment.toProto())
 
     // Add all the children first...
-    element.children.fastForEach { curvedChild ->
+    element.children.forEach { curvedChild ->
         if (curvedChild is EmittableCurvedChild) {
-            curvedChild.children.fastForEach {
+            curvedChild.children.forEach {
                 arcBuilder.addContent(
                     translateEmittableElementInArc(
                         context,
diff --git a/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/curved/CurvedRow.kt b/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/curved/CurvedRow.kt
index 06e5801..58489b1 100644
--- a/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/curved/CurvedRow.kt
+++ b/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/curved/CurvedRow.kt
@@ -18,8 +18,6 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastMap
 import androidx.glance.Emittable
 import androidx.glance.EmittableWithChildren
 import androidx.glance.GlanceModifier
@@ -125,7 +123,7 @@
     curvedScopeImpl.apply(content)
 
     return {
-        curvedChildList.fastForEach { composable ->
+        curvedChildList.forEach { composable ->
             object : CurvedChildScope {}.apply { composable() }
         }
     }
@@ -157,7 +155,7 @@
         it.anchorDegrees = anchorDegrees
         it.anchorType = anchorType
         it.radialAlignment = radialAlignment
-        it.children.addAll(children.fastMap { it.copy() })
+        it.children.addAll(children.map { it.copy() })
     }
 
     override fun toString(): String =
@@ -173,7 +171,7 @@
     override fun copy(): Emittable = EmittableCurvedChild().also {
         it.modifier = modifier
         it.rotateContent = rotateContent
-        it.children.addAll(children.fastMap { it.copy() })
+        it.children.addAll(children.map { it.copy() })
     }
 
     override fun toString(): String = "EmittableCurvedChild(" +
diff --git a/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/template/SingleEntityTemplateLayouts.kt b/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/template/SingleEntityTemplateLayouts.kt
index f22097a..8d5016b 100644
--- a/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/template/SingleEntityTemplateLayouts.kt
+++ b/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/template/SingleEntityTemplateLayouts.kt
@@ -114,7 +114,6 @@
     }
 }
 
-@Suppress("ListIterator")
 @Composable
 private fun TextSection(textList: List<TemplateText>) {
     if (textList.isEmpty()) return
diff --git a/glance/glance-wear-tiles/src/test/kotlin/androidx/glance/wear/tiles/BackgroundTest.kt b/glance/glance-wear-tiles/src/test/kotlin/androidx/glance/wear/tiles/BackgroundTest.kt
index 8018dd2..6b9a108 100644
--- a/glance/glance-wear-tiles/src/test/kotlin/androidx/glance/wear/tiles/BackgroundTest.kt
+++ b/glance/glance-wear-tiles/src/test/kotlin/androidx/glance/wear/tiles/BackgroundTest.kt
@@ -19,9 +19,13 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.toArgb
 import androidx.glance.BackgroundModifier
+import androidx.glance.ColorFilter
 import androidx.glance.GlanceModifier
+import androidx.glance.ImageProvider
+import androidx.glance.TintColorFilterParams
 import androidx.glance.background
 import androidx.glance.findModifier
+import androidx.glance.unit.ColorProvider
 import androidx.glance.unit.FixedColorProvider
 import androidx.glance.unit.ResourceColorProvider
 import androidx.glance.wear.tiles.test.R
@@ -32,9 +36,10 @@
 class BackgroundTest {
     @Test
     fun canUseBackgroundModifier() {
-        val modifier = GlanceModifier.background(color = Color(0xFF223344))
+        val modifier: GlanceModifier = GlanceModifier.background(color = Color(0xFF223344))
 
-        val addedModifier = requireNotNull(modifier.findModifier<BackgroundModifier>())
+        val addedModifier: BackgroundModifier.Color =
+            requireNotNull(modifier.findModifier<BackgroundModifier.Color>())
 
         val modifierColors = addedModifier.colorProvider
         assertIs<FixedColorProvider>(modifierColors)
@@ -48,10 +53,30 @@
     fun canUseBackgroundModifier_resId() {
         val modifier = GlanceModifier.background(color = R.color.color1)
 
-        val addedModifier = requireNotNull(modifier.findModifier<BackgroundModifier>())
+        val addedModifier: BackgroundModifier.Color =
+            requireNotNull(modifier.findModifier<BackgroundModifier.Color>())
 
         val modifierColors = addedModifier.colorProvider
         assertIs<ResourceColorProvider>(modifierColors)
         assertThat(modifierColors.resId).isEqualTo(R.color.color1)
     }
+
+    @Test
+    fun canUseBackgroundModifier_colorFilteredImage() {
+        fun tintColor() = ColorProvider(Color.Magenta)
+
+        val modifier = GlanceModifier.background(
+            ImageProvider(R.drawable.oval), ColorFilter.tint(
+                tintColor()
+            )
+        )
+
+        val addedModifier: BackgroundModifier.Image =
+            requireNotNull(modifier.findModifier<BackgroundModifier.Image>())
+
+        assertThat(
+            (addedModifier.colorFilter?.colorFilterParams as TintColorFilterParams).colorProvider)
+            .isEqualTo(tintColor()
+        )
+    }
 }
diff --git a/glance/glance/api/current.txt b/glance/glance/api/current.txt
index 818bc2f..e87350d 100644
--- a/glance/glance/api/current.txt
+++ b/glance/glance/api/current.txt
@@ -2,6 +2,7 @@
 package androidx.glance {
 
   public final class BackgroundKt {
+    method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.ImageProvider imageProvider, androidx.glance.ColorFilter? tint, optional int contentScale);
     method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.ImageProvider imageProvider, optional int contentScale);
     method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.unit.ColorProvider colorProvider);
     method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, @ColorRes int color);
@@ -128,8 +129,11 @@
 
   public final class ActionKt {
     method public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, androidx.glance.action.Action onClick);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional String? key, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    method public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, androidx.glance.action.Action onClick, optional @DrawableRes int rippleOverride);
+    method @androidx.compose.runtime.Composable public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional @DrawableRes int rippleOverride, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional String? key, optional @DrawableRes int rippleOverride, kotlin.jvm.functions.Function0<kotlin.Unit> block);
     method @androidx.compose.runtime.Composable public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    field @DrawableRes public static final int NoRippleOverride = 0; // 0x0
   }
 
   public abstract class ActionParameters {
diff --git a/glance/glance/api/restricted_current.txt b/glance/glance/api/restricted_current.txt
index 818bc2f..e87350d 100644
--- a/glance/glance/api/restricted_current.txt
+++ b/glance/glance/api/restricted_current.txt
@@ -2,6 +2,7 @@
 package androidx.glance {
 
   public final class BackgroundKt {
+    method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.ImageProvider imageProvider, androidx.glance.ColorFilter? tint, optional int contentScale);
     method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.ImageProvider imageProvider, optional int contentScale);
     method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.unit.ColorProvider colorProvider);
     method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, @ColorRes int color);
@@ -128,8 +129,11 @@
 
   public final class ActionKt {
     method public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, androidx.glance.action.Action onClick);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional String? key, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    method public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, androidx.glance.action.Action onClick, optional @DrawableRes int rippleOverride);
+    method @androidx.compose.runtime.Composable public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional @DrawableRes int rippleOverride, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional String? key, optional @DrawableRes int rippleOverride, kotlin.jvm.functions.Function0<kotlin.Unit> block);
     method @androidx.compose.runtime.Composable public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    field @DrawableRes public static final int NoRippleOverride = 0; // 0x0
   }
 
   public abstract class ActionParameters {
diff --git a/glance/glance/build.gradle b/glance/glance/build.gradle
index a27703a..0a8e136 100644
--- a/glance/glance/build.gradle
+++ b/glance/glance/build.gradle
@@ -25,7 +25,6 @@
 
 dependencies {
 
-    api(project(":compose:ui:ui-util"))
     api("androidx.annotation:annotation:1.2.0")
     api("androidx.compose.runtime:runtime:1.2.1")
     api("androidx.compose.ui:ui-graphics:1.1.1")
diff --git a/glance/glance/lint-baseline.xml b/glance/glance/lint-baseline.xml
index 2f50602..7b47edb 100644
--- a/glance/glance/lint-baseline.xml
+++ b/glance/glance/lint-baseline.xml
@@ -20,6 +20,87 @@
     </issue>
 
     <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        it.children.addAll(children.map { it.copy() })"
+        errorLine2="                                    ~~~">
+        <location
+            file="src/main/java/androidx/glance/layout/Box.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        it.children.addAll(children.map { it.copy() })"
+        errorLine2="                                    ~~~">
+        <location
+            file="src/main/java/androidx/glance/layout/Column.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        children.joinToString(&quot;,\n&quot;).prependIndent(&quot;  &quot;)"
+        errorLine2="                 ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/Emittables.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            events.forEach { addAction(it) }"
+        errorLine2="                   ~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/session/IdleEventBroadcastReceiver.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="    fold(0.dp) { acc, res ->"
+        errorLine2="    ~~~~">
+        <location
+            file="src/main/java/androidx/glance/layout/Padding.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        it.children.addAll(children.map { it.copy() })"
+        errorLine2="                                    ~~~">
+        <location
+            file="src/main/java/androidx/glance/layout/Row.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            .any { it.state == WorkInfo.State.RUNNING } &amp;&amp; synchronized(sessions) {"
+        errorLine2="             ~~~">
+        <location
+            file="src/main/java/androidx/glance/session/SessionManager.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="            val mask = decorations.fold(0) { acc, decoration ->"
+        errorLine2="                                   ~~~~">
+        <location
+            file="src/main/java/androidx/glance/text/TextDecoration.kt"/>
+    </issue>
+
+    <issue
+        id="ListIterator"
+        message="Creating an unnecessary Iterator to iterate through a List"
+        errorLine1="        return &quot;TextDecoration[${values.joinToString(separator = &quot;, &quot;)}]&quot;"
+        errorLine2="                                        ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/glance/text/TextDecoration.kt"/>
+    </issue>
+
+    <issue
         id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in constructor InteractiveFrameClock has parameter &apos;nanoTime&apos; with type Function0&lt;Long>."
         errorLine1="    private val nanoTime: () -> Long = { System.nanoTime() }"
diff --git a/glance/glance/src/main/java/androidx/glance/Background.kt b/glance/glance/src/main/java/androidx/glance/Background.kt
index 67e1195..4b254af 100644
--- a/glance/glance/src/main/java/androidx/glance/Background.kt
+++ b/glance/glance/src/main/java/androidx/glance/Background.kt
@@ -23,26 +23,22 @@
 import androidx.glance.unit.ColorProvider
 
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class BackgroundModifier private constructor(
-    val colorProvider: ColorProvider?,
-    val imageProvider: ImageProvider?,
-    val contentScale: ContentScale = ContentScale.FillBounds,
-) : GlanceModifier.Element {
-    init {
-        require((colorProvider != null) xor (imageProvider != null)) {
-            "Exactly one of colorProvider and imageProvider must be non-null"
-        }
+sealed interface BackgroundModifier : GlanceModifier.Element {
+
+    class Color(val colorProvider: ColorProvider) : BackgroundModifier {
+        override fun toString() =
+            "BackgroundModifier(colorProvider=$colorProvider)"
     }
 
-    constructor(colorProvider: ColorProvider) :
-        this(colorProvider = colorProvider, imageProvider = null)
-
-    constructor(imageProvider: ImageProvider, contentScale: ContentScale) :
-        this(colorProvider = null, imageProvider = imageProvider, contentScale = contentScale)
-
-    override fun toString() =
-        "BackgroundModifier(colorProvider=$colorProvider, imageProvider=$imageProvider, " +
-            "contentScale=$contentScale)"
+    class Image(
+        val imageProvider: ImageProvider?,
+        val contentScale: ContentScale = ContentScale.FillBounds,
+        val colorFilter: ColorFilter? = null
+    ) : BackgroundModifier {
+        override fun toString() =
+            "BackgroundModifier(colorFilter=$colorFilter, imageProvider=$imageProvider, " +
+                "contentScale=$contentScale)"
+    }
 }
 
 /**
@@ -67,7 +63,7 @@
  * the element.
  */
 fun GlanceModifier.background(colorProvider: ColorProvider): GlanceModifier =
-    this.then(BackgroundModifier(colorProvider))
+    this.then(BackgroundModifier.Color(colorProvider))
 
 /**
  * Apply a background image to the element this modifier is attached to.
@@ -76,4 +72,20 @@
     imageProvider: ImageProvider,
     contentScale: ContentScale = ContentScale.FillBounds
 ): GlanceModifier =
-    this.then(BackgroundModifier(imageProvider, contentScale))
+    this.then(BackgroundModifier.Image(imageProvider, contentScale))
+
+/**
+ * Apply a background image to the element this modifier is attached to.
+ */
+fun GlanceModifier.background(
+    imageProvider: ImageProvider,
+    tint: ColorFilter?,
+    contentScale: ContentScale = ContentScale.FillBounds,
+    ): GlanceModifier =
+    this.then(
+        BackgroundModifier.Image(
+            imageProvider = imageProvider,
+            contentScale = contentScale,
+            colorFilter = tint
+        )
+    )
diff --git a/glance/glance/src/main/java/androidx/glance/Emittables.kt b/glance/glance/src/main/java/androidx/glance/Emittables.kt
index 84d50c2..90a8168 100644
--- a/glance/glance/src/main/java/androidx/glance/Emittables.kt
+++ b/glance/glance/src/main/java/androidx/glance/Emittables.kt
@@ -17,7 +17,6 @@
 package androidx.glance
 
 import androidx.annotation.RestrictTo
-import androidx.compose.ui.util.fastJoinToString
 import androidx.glance.layout.Alignment
 import androidx.glance.text.TextStyle
 
@@ -35,7 +34,17 @@
     val children: MutableList<Emittable> = mutableListOf<Emittable>()
 
     protected fun childrenToString(): String =
-        children.fastJoinToString(",\n").prependIndent("  ")
+        children.joinToString(",\n").prependIndent("  ")
+}
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun EmittableWithChildren.addChild(e: Emittable) {
+    this.children += e
+}
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun EmittableWithChildren.addChildIfNotNull(e: Emittable?) {
+    if (e != null) this.children += e
 }
 
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
diff --git a/glance/glance/src/main/java/androidx/glance/Image.kt b/glance/glance/src/main/java/androidx/glance/Image.kt
index ae5f43e..4011c8c 100644
--- a/glance/glance/src/main/java/androidx/glance/Image.kt
+++ b/glance/glance/src/main/java/androidx/glance/Image.kt
@@ -86,7 +86,9 @@
 /**
  * Effects used to modify the color of an image.
  */
-class ColorFilter internal constructor(internal val colorFilterParams: ColorFilterParams) {
+class ColorFilter internal constructor(
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) val colorFilterParams: ColorFilterParams
+) {
     companion object {
         /**
          * Set a tinting option for the image using the platform-specific default blending mode.
diff --git a/glance/glance/src/main/java/androidx/glance/action/Action.kt b/glance/glance/src/main/java/androidx/glance/action/Action.kt
index d214afe..91028c5e 100644
--- a/glance/glance/src/main/java/androidx/glance/action/Action.kt
+++ b/glance/glance/src/main/java/androidx/glance/action/Action.kt
@@ -17,6 +17,7 @@
 package androidx.glance.action
 
 import android.app.Activity
+import androidx.annotation.DrawableRes
 import androidx.annotation.RestrictTo
 import androidx.compose.runtime.Composable
 import androidx.glance.ExperimentalGlanceApi
@@ -31,12 +32,16 @@
 
 /**
  * Apply an [Action], to be executed in response to a user click.
+ *
+ * @param onClick The action to run.
  */
 fun GlanceModifier.clickable(onClick: Action): GlanceModifier =
     this.then(ActionModifier(onClick))
 
 /**
  * Run [block] in response to a user click.
+ *
+ * @param block The action to run.
  */
 @Composable
 fun GlanceModifier.clickable(
@@ -47,7 +52,36 @@
 /**
  * Run [block] in response to a user click.
  *
+ * @param rippleOverride A drawable resource to use as the onClick ripple. Use [NoRippleOverride]
+ * if no custom behavior is needed.
+ * @param block The action to run.v
+ */
+@Composable
+fun GlanceModifier.clickable(
+    @DrawableRes rippleOverride: Int = NoRippleOverride,
+    block: () -> Unit
+): GlanceModifier =
+    this.then(ActionModifier(action = action(block = block), rippleOverride = rippleOverride))
+
+/**
+ * Apply an [Action], to be executed in response to a user click.
+ *
+ * @param rippleOverride A drawable resource to use as the onClick ripple. Use [NoRippleOverride]
+ * if no custom behavior is needed.
+ * @param onClick The action to run.
+ */
+fun GlanceModifier.clickable(
+    onClick: Action,
+    @DrawableRes rippleOverride: Int = NoRippleOverride
+): GlanceModifier =
+    this.then(ActionModifier(action = onClick, rippleOverride = rippleOverride))
+
+/**
+ * Run [block] in response to a user click.
+ *
  * @param block The action to run.
+ * @param rippleOverride A drawable resource to use as the onClick ripple. Use [NoRippleOverride]
+ * if no custom behavior is needed.
  * @param key A stable and unique key that identifies the action for this element. This ensures
  * that the correct action is triggered, especially in cases of items that change order. If not
  * provided we use the key that is automatically generated by the Compose runtime, which is unique
@@ -57,13 +91,24 @@
 @Composable
 fun GlanceModifier.clickable(
     key: String? = null,
+    @DrawableRes rippleOverride: Int = NoRippleOverride,
     block: () -> Unit
 ): GlanceModifier =
-    this.then(ActionModifier(action(key, block)))
+    this.then(ActionModifier(action = action(key, block), rippleOverride = rippleOverride))
 
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class ActionModifier(val action: Action) : GlanceModifier.Element {
+class ActionModifier(
+    val action: Action,
+    @DrawableRes val rippleOverride: Int = NoRippleOverride
+) : GlanceModifier.Element {
     override fun toString(): String {
-        return "ActionModifier(action=$action)"
+        return "ActionModifier(action=$action, rippleOverride=$rippleOverride)"
     }
 }
+
+/**
+ * Constant. Tells the system that there is no ripple override. When this is passed, the system
+ * will use default behavior for the ripple.
+ */
+@DrawableRes
+const val NoRippleOverride = 0
diff --git a/glance/glance/src/main/java/androidx/glance/layout/Box.kt b/glance/glance/src/main/java/androidx/glance/layout/Box.kt
index 9392c48..acc6c2e 100644
--- a/glance/glance/src/main/java/androidx/glance/layout/Box.kt
+++ b/glance/glance/src/main/java/androidx/glance/layout/Box.kt
@@ -18,7 +18,6 @@
 
 import androidx.annotation.RestrictTo
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.util.fastMap
 import androidx.glance.Emittable
 import androidx.glance.EmittableWithChildren
 import androidx.glance.GlanceModifier
@@ -33,7 +32,7 @@
     override fun copy(): Emittable = EmittableBox().also {
         it.modifier = modifier
         it.contentAlignment = contentAlignment
-        it.children.addAll(children.fastMap { it.copy() })
+        it.children.addAll(children.map { it.copy() })
     }
 
     override fun toString(): String = "EmittableBox(" +
diff --git a/glance/glance/src/main/java/androidx/glance/layout/Column.kt b/glance/glance/src/main/java/androidx/glance/layout/Column.kt
index d489c7f..6e1e4e1 100644
--- a/glance/glance/src/main/java/androidx/glance/layout/Column.kt
+++ b/glance/glance/src/main/java/androidx/glance/layout/Column.kt
@@ -18,7 +18,6 @@
 
 import androidx.annotation.RestrictTo
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.util.fastMap
 import androidx.glance.Emittable
 import androidx.glance.EmittableWithChildren
 import androidx.glance.GlanceModifier
@@ -35,7 +34,7 @@
         it.modifier = modifier
         it.verticalAlignment = verticalAlignment
         it.horizontalAlignment = horizontalAlignment
-        it.children.addAll(children.fastMap { it.copy() })
+        it.children.addAll(children.map { it.copy() })
     }
 
     override fun toString(): String = "EmittableColumn(" +
diff --git a/glance/glance/src/main/java/androidx/glance/layout/Padding.kt b/glance/glance/src/main/java/androidx/glance/layout/Padding.kt
index 95ebe2c..a5027be 100644
--- a/glance/glance/src/main/java/androidx/glance/layout/Padding.kt
+++ b/glance/glance/src/main/java/androidx/glance/layout/Padding.kt
@@ -20,7 +20,6 @@
 import androidx.annotation.RestrictTo
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastFold
 import androidx.glance.GlanceModifier
 
 /**
@@ -194,7 +193,7 @@
     collectPadding()?.toDp(resources)
 
 private fun List<Int>.toDp(resources: Resources) =
-    fastFold(0.dp) { acc, res ->
+    fold(0.dp) { acc, res ->
         acc + (resources.getDimension(res) / resources.displayMetrics.density).dp
     }
 
diff --git a/glance/glance/src/main/java/androidx/glance/layout/Row.kt b/glance/glance/src/main/java/androidx/glance/layout/Row.kt
index fa7bb50..4fe0201 100644
--- a/glance/glance/src/main/java/androidx/glance/layout/Row.kt
+++ b/glance/glance/src/main/java/androidx/glance/layout/Row.kt
@@ -18,7 +18,6 @@
 
 import androidx.annotation.RestrictTo
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.util.fastMap
 import androidx.glance.Emittable
 import androidx.glance.EmittableWithChildren
 import androidx.glance.GlanceModifier
@@ -35,7 +34,7 @@
         it.modifier = modifier
         it.horizontalAlignment = horizontalAlignment
         it.verticalAlignment = verticalAlignment
-        it.children.addAll(children.fastMap { it.copy() })
+        it.children.addAll(children.map { it.copy() })
     }
 
     override fun toString(): String = "EmittableRow(" +
diff --git a/glance/glance/src/main/java/androidx/glance/session/IdleEventBroadcastReceiver.kt b/glance/glance/src/main/java/androidx/glance/session/IdleEventBroadcastReceiver.kt
index 5801903..7929994 100644
--- a/glance/glance/src/main/java/androidx/glance/session/IdleEventBroadcastReceiver.kt
+++ b/glance/glance/src/main/java/androidx/glance/session/IdleEventBroadcastReceiver.kt
@@ -34,7 +34,6 @@
             PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED,
             PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED
         )
-        @Suppress("ListIterator")
         val filter = IntentFilter().apply {
             events.forEach { addAction(it) }
         }
diff --git a/glance/glance/src/main/java/androidx/glance/session/Session.kt b/glance/glance/src/main/java/androidx/glance/session/Session.kt
index 673b47a..c7aec1e 100644
--- a/glance/glance/src/main/java/androidx/glance/session/Session.kt
+++ b/glance/glance/src/main/java/androidx/glance/session/Session.kt
@@ -17,6 +17,7 @@
 package androidx.glance.session
 
 import android.content.Context
+import android.util.Log
 import androidx.annotation.RestrictTo
 import androidx.compose.runtime.Composable
 import androidx.glance.EmittableWithChildren
@@ -87,4 +88,12 @@
     fun close() {
         eventChannel.close()
     }
+
+    /**
+     * Called when there is an error in the composition. The session will be closed immediately
+     * after this.
+     */
+    open suspend fun onCompositionError(context: Context, throwable: Throwable) {
+        Log.e("GlanceSession", "Error running composition", throwable)
+    }
 }
diff --git a/glance/glance/src/main/java/androidx/glance/session/SessionManager.kt b/glance/glance/src/main/java/androidx/glance/session/SessionManager.kt
index 3213cd3..5b83da2 100644
--- a/glance/glance/src/main/java/androidx/glance/session/SessionManager.kt
+++ b/glance/glance/src/main/java/androidx/glance/session/SessionManager.kt
@@ -96,7 +96,6 @@
     }
 
     override suspend fun isSessionRunning(context: Context, key: String) =
-        @Suppress("ListIterator")
         (WorkManager.getInstance(context).getWorkInfosForUniqueWork(key).await()
             .any { it.state == WorkInfo.State.RUNNING } && synchronized(sessions) {
             sessions.containsKey(key)
diff --git a/glance/glance/src/main/java/androidx/glance/session/SessionWorker.kt b/glance/glance/src/main/java/androidx/glance/session/SessionWorker.kt
index 79e6e40..72fd733 100644
--- a/glance/glance/src/main/java/androidx/glance/session/SessionWorker.kt
+++ b/glance/glance/src/main/java/androidx/glance/session/SessionWorker.kt
@@ -27,8 +27,11 @@
 import androidx.work.WorkerParameters
 import kotlin.time.Duration
 import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineExceptionHandler
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.collectLatest
@@ -113,14 +116,32 @@
         val frameClock = InteractiveFrameClock(this)
         val snapshotMonitor = launch { globalSnapshotMonitor() }
         val root = session.createRootEmittable()
-        val recomposer = Recomposer(coroutineContext)
-        val composition = Composition(Applier(root), recomposer).apply {
-            setContent(session.provideGlance(applicationContext))
-        }
         val uiReady = MutableStateFlow(false)
+        // Use an independent Job with a CoroutineExceptionHandler so that we can catch errors from
+        // LaunchedEffects in the composition and they won't propagate up to the Worker context.
+        val effectExceptionHandler = CoroutineExceptionHandler { _, throwable ->
+            launch {
+                session.onCompositionError(applicationContext, throwable)
+                session.close()
+                uiReady.emit(true)
+            }
+        }
+        val effectCoroutineContext = coroutineContext + Job() + effectExceptionHandler
+        val recomposer = Recomposer(effectCoroutineContext)
+        val composition = Composition(Applier(root), recomposer)
 
         launch(frameClock) {
-            recomposer.runRecomposeAndApplyChanges()
+            try {
+                composition.setContent(session.provideGlance(applicationContext))
+                recomposer.runRecomposeAndApplyChanges()
+            } catch (e: CancellationException) {
+                // do nothing if we are cancelled.
+            } catch (throwable: Throwable) {
+                session.onCompositionError(applicationContext, throwable)
+                session.close()
+                // Set uiReady to true to resume coroutine waiting on it.
+                uiReady.emit(true)
+            }
         }
         launch {
             var lastRecomposeCount = recomposer.changeCount
diff --git a/glance/glance/src/main/java/androidx/glance/text/TextDecoration.kt b/glance/glance/src/main/java/androidx/glance/text/TextDecoration.kt
index 386a8e2..6f100c0 100644
--- a/glance/glance/src/main/java/androidx/glance/text/TextDecoration.kt
+++ b/glance/glance/src/main/java/androidx/glance/text/TextDecoration.kt
@@ -17,8 +17,6 @@
 package androidx.glance.text
 
 import androidx.compose.runtime.Stable
-import androidx.compose.ui.util.fastFold
-import androidx.compose.ui.util.fastJoinToString
 
 /**
  * Defines a horizontal line to be drawn on the text.
@@ -46,7 +44,7 @@
          * @param decorations The decorations to be added
          */
         fun combine(decorations: List<TextDecoration>): TextDecoration {
-            val mask = decorations.fastFold(0) { acc, decoration ->
+            val mask = decorations.fold(0) { acc, decoration ->
                 acc or decoration.mask
             }
             return TextDecoration(mask)
@@ -83,6 +81,6 @@
         if ((values.size == 1)) {
             return "TextDecoration.${values[0]}"
         }
-        return "TextDecoration[${values.fastJoinToString(separator = ", ")}]"
+        return "TextDecoration[${values.joinToString(separator = ", ")}]"
     }
 }
diff --git a/glance/glance/src/test/kotlin/androidx/glance/session/SessionWorkerTest.kt b/glance/glance/src/test/kotlin/androidx/glance/session/SessionWorkerTest.kt
index ccbd801..c62f9c0 100644
--- a/glance/glance/src/test/kotlin/androidx/glance/session/SessionWorkerTest.kt
+++ b/glance/glance/src/test/kotlin/androidx/glance/session/SessionWorkerTest.kt
@@ -18,6 +18,8 @@
 
 import android.content.Context
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.mutableStateOf
 import androidx.glance.EmittableWithChildren
 import androidx.glance.GlanceComposable
@@ -88,7 +90,7 @@
             Box {
                 Text("Hello World")
             }
-        }.first()
+        }.first().getOrThrow()
         val box = assertIs<EmittableBox>(root.children.single())
         val text = assertIs<EmittableText>(box.children.single())
         assertThat(text.text).isEqualTo("Hello World")
@@ -118,13 +120,13 @@
         val uiFlow = sessionManager.startSession(context) {
                 Text(state.value)
         }
-        uiFlow.first().let { root ->
+        uiFlow.first().getOrThrow().let { root ->
             val text = assertIs<EmittableText>(root.children.single())
             assertThat(text.text).isEqualTo("Hello World")
         }
 
         state.value = "Hello Earth"
-        uiFlow.first().let { root ->
+        uiFlow.first().getOrThrow().let { root ->
             val text = assertIs<EmittableText>(root.children.single())
             assertThat(text.text).isEqualTo("Hello Earth")
         }
@@ -142,7 +144,7 @@
         val uiFlow = sessionManager.startSession(context) {
             Text(state.value)
         }
-        uiFlow.first().let { root ->
+        uiFlow.first().getOrThrow().let { root ->
             val text = assertIs<EmittableText>(root.children.single())
             assertThat(text.text).isEqualTo("Hello World")
         }
@@ -150,7 +152,7 @@
         session.sendEvent {
             state.value = "Hello Earth"
         }
-        uiFlow.first().let { root ->
+        uiFlow.first().getOrThrow().let { root ->
             val text = assertIs<EmittableText>(root.children.single())
             assertThat(text.text).isEqualTo("Hello Earth")
         }
@@ -168,7 +170,7 @@
         val uiFlow = sessionManager.startDelayedProcessingSession(context) {
             Text(state.value)
         }
-        uiFlow.first().let { root ->
+        uiFlow.first().getOrThrow().let { root ->
             val text = assertIs<EmittableText>(root.children.single())
             assertThat(text.text).isEqualTo("Hello World")
         }
@@ -176,7 +178,7 @@
         // Changing the value triggers recomposition, which should cancel the currently running call
         // to processEmittableTree.
         state.value = "Hello Earth"
-        uiFlow.first().let { root ->
+        uiFlow.first().getOrThrow().let { root ->
             val text = assertIs<EmittableText>(root.children.single())
             assertThat(text.text).isEqualTo("Hello Earth")
         }
@@ -185,6 +187,86 @@
         assertThat(session.processEmittableTreeCancelCount).isEqualTo(1)
         sessionManager.closeSession()
     }
+
+    @Test
+    fun sessionWorkerCatchesCompositionError() = runTest {
+        launch {
+            val result = worker.doWork()
+            assertThat(result).isEqualTo(Result.success())
+        }
+
+        val cause = Throwable()
+        val exception = Exception("message", cause)
+        val result = sessionManager.startSession(context) {
+            throw exception
+        }.first().exceptionOrNull()
+        assertThat(result).hasCauseThat().isEqualTo(cause)
+        assertThat(result).hasMessageThat().isEqualTo("message")
+    }
+
+    @Test
+    fun sessionWorkerCatchesRecompositionError() = runTest {
+        launch {
+            val result = worker.doWork()
+            assertThat(result).isEqualTo(Result.success())
+        }
+
+        val runError = mutableStateOf(false)
+        val cause = Throwable()
+        val exception = Exception("message", cause)
+        val resultFlow = sessionManager.startSession(context) {
+            if (runError.value) {
+                throw exception
+            } else {
+                Text("Hello World")
+            }
+        }
+
+        resultFlow.first().getOrThrow().let { root ->
+            val text = assertIs<EmittableText>(root.children.single())
+            assertThat(text.text).isEqualTo("Hello World")
+        }
+
+        runError.value = true
+        val result = resultFlow.first().exceptionOrNull()
+        // Errors thrown on recomposition are wrapped in an identical outer exception with the
+        // original exception as the `cause`.
+        assertThat(result).hasCauseThat().isEqualTo(exception)
+        assertThat(result?.cause?.cause).isEqualTo(cause)
+        assertThat(result).hasMessageThat().isEqualTo("message")
+    }
+
+    @Test
+    fun sessionWorkerCatchesSideEffectError() = runTest {
+        launch {
+            val result = worker.doWork()
+            assertThat(result).isEqualTo(Result.success())
+        }
+
+        val cause = Throwable()
+        val exception = Exception("message", cause)
+        val result = sessionManager.startSession(context) {
+            SideEffect { throw exception }
+        }.first().exceptionOrNull()
+        assertThat(result).hasCauseThat().isEqualTo(cause)
+        assertThat(result).hasMessageThat().isEqualTo("message")
+    }
+
+    @Test
+    fun sessionWorkerCatchesLaunchedEffectError() = runTest {
+        launch {
+            val result = worker.doWork()
+            assertThat(result).isEqualTo(Result.success())
+        }
+
+        val cause = Throwable()
+        val exception = Exception("message", cause)
+        val result = sessionManager.startSession(context) {
+            LaunchedEffect(true) { throw exception }
+        }.first().exceptionOrNull()
+        assertThat(result).hasCauseThat().isEqualTo(cause)
+        assertThat(result).hasMessageThat().isEqualTo("message")
+    }
 }
 
 private const val SESSION_KEY = "123"
@@ -195,18 +277,18 @@
     suspend fun startSession(
         context: Context,
         content: @GlanceComposable @Composable () -> Unit = {}
-    ) = MutableSharedFlow<EmittableWithChildren>().also { flow ->
-        startSession(context, TestSession(onUiFlow = flow, content = content))
+    ) = MutableSharedFlow<kotlin.Result<EmittableWithChildren>>().also { flow ->
+        startSession(context, TestSession(resultFlow = flow, content = content))
     }
 
     suspend fun startDelayedProcessingSession(
         context: Context,
         content: @GlanceComposable @Composable () -> Unit = {}
-    ) = MutableSharedFlow<EmittableWithChildren>().also { flow ->
+    ) = MutableSharedFlow<kotlin.Result<EmittableWithChildren>>().also { flow ->
         startSession(
             context,
             TestSession(
-                onUiFlow = flow,
+                resultFlow = flow,
                 content = content,
                 processEmittableTreeHasInfiniteDelay = true,
             )
@@ -234,7 +316,7 @@
 
 class TestSession(
     key: String = SESSION_KEY,
-    val onUiFlow: MutableSharedFlow<EmittableWithChildren>? = null,
+    val resultFlow: MutableSharedFlow<kotlin.Result<EmittableWithChildren>>? = null,
     val content: @GlanceComposable @Composable () -> Unit = {},
     var processEmittableTreeHasInfiniteDelay: Boolean = false,
 ) : Session(key) {
@@ -257,7 +339,7 @@
         context: Context,
         root: EmittableWithChildren
     ): Boolean {
-        onUiFlow?.emit(root)
+        resultFlow?.emit(kotlin.Result.success(root))
         try {
             if (processEmittableTreeHasInfiniteDelay) {
                 delay(Duration.INFINITE)
@@ -274,4 +356,8 @@
         require(event is Function0<*>)
         event.invoke()
     }
+
+    override suspend fun onCompositionError(context: Context, throwable: Throwable) {
+        resultFlow?.emit(kotlin.Result.failure(throwable))
+    }
 }
diff --git a/gradle/README.md b/gradle/README.md
index e945e4e..bd7a571 100644
--- a/gradle/README.md
+++ b/gradle/README.md
@@ -24,24 +24,13 @@
 
 [Configuration file for Gradle dependency verification](https://docs.gradle.org/current/userguide/dependency_verification.html#sub:verification-metadata) used by androidx to make sure dependencies are [signed with trusted signatures](https://docs.gradle.org/current/userguide/dependency_verification.html#sec:signature-verificationn) and that unsigned artifacts have [expected checksums](https://docs.gradle.org/current/userguide/dependency_verification.html#sec:checksum-verification).
 
-When adding a new artifact
-- if it is signed, then run:
+When adding a new artifact, first run:
 ```
 development/update-verification-metadata.sh
 ```
-to trust the signature of the new artifact.
+to trust the signature (or checksum) of the new artifact.
 
-- if it is not signed, then run the following to add generated checksums to `verification-metadata.xml`:
-
-```
-./gradlew -M sha256 buildOnServer --dry-run
-```
-
-Then you will want to diff `gradle/verification-metadata.dryrun.xml` and
-`gradle/verification-metadata.xml` using your favorite tool (e.g. meld) can copy over the entries
-that are relevant to your new artifacts.
-
-Each new checksum that you copy over in this way must be associated with a bug that is tracking
+Then, if any checksums were added, make sure they're associated with a bug that is tracking
 an effort to build or acquire a signed version of this dependency.  To associate with a bug,
 please add an `androidx:reason` attribute to a string that contains a URL for a bug filed
 either in buganizer or github:
@@ -57,8 +46,6 @@
 </component>
 ```
 
-After doing this, you can then delete all the `verification-*-dryrun.*` files.
-
 ### If that doesn't work.
 
 If the artifact is not signed, and does not get automatically added to
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index db17c37..bffdc0b 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -26,14 +26,14 @@
 byteBuddy = "1.12.10"
 asm = "9.3"
 cmake = "3.22.1"
-dagger = "2.46.1"
+dagger = "2.48"
 dexmaker = "2.28.3"
 dokka = "1.8.20-dev-214"
 espresso = "3.6.0-alpha01"
 espressoDevice = "1.0.0-alpha05"
 grpc = "1.52.0"
 guavaJre = "31.1-jre"
-hilt = "2.46.1"
+hilt = "2.48"
 incap = "0.2"
 jcodec = "0.2.5"
 kotlin17 = "1.7.10"
@@ -57,7 +57,7 @@
 paparazzi = "1.0.0"
 paparazziNative = "2022.1.1-canary-f5f9f71"
 skiko = "0.7.7"
-spdxGradlePlugin = "0.1.0"
+spdxGradlePlugin = "0.2.0"
 sqldelight = "1.3.0"
 retrofit = "2.7.2"
 wire = "4.7.0"
@@ -230,11 +230,11 @@
 opentest4j = { module = "org.opentest4j:opentest4j", version = "1.2.0" }
 playFeatureDelivery = { module = "com.google.android.play:feature-delivery", version = "2.0.1" }
 playCore = { module = "com.google.android.play:core", version = "1.10.3" }
-playServicesAuth = {module = "com.google.android.gms:play-services-auth", version = "20.5.0"}
+playServicesAuth = {module = "com.google.android.gms:play-services-auth", version = "20.7.0"}
 playServicesBase = { module = "com.google.android.gms:play-services-base", version = "17.0.0" }
 playServicesBasement = { module = "com.google.android.gms:play-services-basement", version = "17.0.0" }
 playServicesDevicePerformance = { module = "com.google.android.gms:play-services-deviceperformance", version = "16.0.0" }
-playServicesFido = {module = "com.google.android.gms:play-services-fido", version = "20.0.1"}
+playServicesFido = {module = "com.google.android.gms:play-services-fido", version = "20.1.0"}
 playServicesWearable = { module = "com.google.android.gms:play-services-wearable", version = "17.1.0" }
 paparazzi = { module = "app.cash.paparazzi:paparazzi", version.ref = "paparazzi" }
 paparazziNativeJvm = { module = "app.cash.paparazzi:layoutlib-native-jdk11", version.ref = "paparazziNative" }
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 18337be..a869424 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -27,7 +27,7 @@
       <trusted-keys>
          <trusted-key id="00089ee8c3afa95a854d0f1df800dd0933ecf7f7" group="com.google.guava"/>
          <trusted-key id="019082bc00e0324e2aef4cf00d3b328562a119a7" group="org.openjdk.jmh"/>
-         <trusted-key id="03c123038c20aae9e286c857479d601f3a7b5c1a" group="com.github.ajalt.clikt" name="clikt-jvm" version="3.5.3"/>
+         <trusted-key id="03c123038c20aae9e286c857479d601f3a7b5c1a" group="com.github.ajalt.clikt" name="clikt-jvm" />
          <trusted-key id="042b29e928995b9db963c636c7ca19b7b620d787" group="org.apache.maven"/>
          <trusted-key id="04543577d6a9cc626239c50c7ecbd740ff06aeb5">
             <trusting group="com.sun.activation"/>
@@ -221,7 +221,7 @@
             <trusting group="com.sun.activation"/>
             <trusting group="jakarta.activation"/>
          </trusted-key>
-         <trusted-key id="6ead752b3e2b38e8e2236d7ba9321edaa5cb3202" group="ch.randelshofer" name="fastdoubleparser" version="0.8.0"/>
+         <trusted-key id="6ead752b3e2b38e8e2236d7ba9321edaa5cb3202" group="ch.randelshofer" name="fastdoubleparser" />
          <trusted-key id="6f538074ccebf35f28af9b066a0975f8b1127b83">
             <trusting group="org.jetbrains.kotlin"/>
             <trusting group="org.jetbrains.kotlin.jvm"/>
@@ -245,9 +245,9 @@
             <trusting group="org.jvnet.staxex"/>
             <trusting group="^com[.]sun($|([.].*))" regex="true"/>
          </trusted-key>
-         <trusted-key id="713da88be50911535fe716f5208b0ab1d63011c7" group="org.apache.tomcat" name="annotations-api" version="6.0.53"/>
+         <trusted-key id="713da88be50911535fe716f5208b0ab1d63011c7" group="org.apache.tomcat" name="annotations-api" />
          <trusted-key id="720746177725a89207a7075bfd5dea07fcb690a8" group="org.codehaus.mojo"/>
-         <trusted-key id="73976c9c39c1479b84e2641a5a68a2249128e2c6" group="com.google.crypto.tink" name="tink-android" version="1.8.0"/>
+         <trusted-key id="73976c9c39c1479b84e2641a5a68a2249128e2c6" group="com.google.crypto.tink" name="tink-android" />
          <trusted-key id="748f15b2cf9ba8f024155e6ed7c92b70fa1c814d" group="org.apache.logging.log4j"/>
          <trusted-key id="7615ad56144df2376f49d98b1669c4bb543e0445" group="com.google.errorprone"/>
          <trusted-key id="7616eb882daf57a11477aaf559a252fb1199d873" group="com.google.code.findbugs"/>
@@ -261,11 +261,11 @@
          <trusted-key id="7e22d50a7ebd9d2cd269b2d4056aca74d46000bf" group="io.netty"/>
          <trusted-key id="7f36e793ae3252e5d9e9b98fee9e7dc9d92fc896" group="com.google.errorprone"/>
          <trusted-key id="7faa0f2206de228f0db01ad741321490758aad6f" group="org.codehaus.groovy"/>
-         <trusted-key id="7fe5e98df3a5c0dc34663ab7c1add37ca0069309" group="org.spdx" name="spdx-gradle-plugin" version="0.1.0"/>
+         <trusted-key id="7fe5e98df3a5c0dc34663ab7c1add37ca0069309" group="org.spdx" name="spdx-gradle-plugin"/>
          <trusted-key id="808d78b17a5a2d7c3668e31fbffc9b54721244ad" group="org.apache.commons"/>
          <trusted-key id="80f6d6b0d90c6747753344cab5a9e81b565e89e0" group="org.tomlj"/>
          <trusted-key id="8254180bfc943b816e0b5e2e5e2f2b3d474efe6b" group="it.unimi.dsi"/>
-         <trusted-key id="82c9ec0e52c47a936a849e0113d979595e6d01e1" group="org.apache.maven.shared" name="maven-shared-utils" version="3.3.4"/>
+         <trusted-key id="82c9ec0e52c47a936a849e0113d979595e6d01e1" group="org.apache.maven.shared" name="maven-shared-utils" />
          <trusted-key id="82f833963889d7ed06f1e4dc6525fd70cc303655" group="org.codehaus.mojo"/>
          <trusted-key id="835a685c8c6f49c54980e5caf406f31bc1468eba" group="org.jcodec"/>
          <trusted-key id="842afb86375d805422835bfd82b5574242c20d6f" group="org.antlr"/>
@@ -274,7 +274,7 @@
             <trusting group="org.apache.maven" name="maven-parent"/>
          </trusted-key>
          <trusted-key id="8569c95cadc508b09fe90f3002216ed811210daa" group="io.github.detekt.sarif4k"/>
-         <trusted-key id="86616cd3c4f0803e73374a434dbf5995d492505d" group="org.json" name="json" version="20230227"/>
+         <trusted-key id="86616cd3c4f0803e73374a434dbf5995d492505d" group="org.json" name="json" />
          <trusted-key id="8756c4f765c9ac3cb6b85d62379ce192d401ab61">
             <trusting group="com.github.ajalt"/>
             <trusting group="com.github.javaparser"/>
@@ -342,7 +342,7 @@
          <trusted-key id="ae9e53fc28ff2ab1012273d0bf1518e0160788a2" group="org.apache" name="apache"/>
          <trusted-key id="afa2b1823fc021bfd08c211fd5f4c07a434ab3da" group="com.squareup"/>
          <trusted-key id="afcc4c7594d09e2182c60e0f7a01b0f236e5430f" group="com.google.code.gson"/>
-         <trusted-key id="b02137d875d833d9b23392ecae5a7fb608a0221c" group="org.codehaus.plexus" name="plexus-classworlds" version="2.6.0"/>
+         <trusted-key id="b02137d875d833d9b23392ecae5a7fb608a0221c" group="org.codehaus.plexus" name="plexus-classworlds" />
          <trusted-key id="b02335aa54ccf21e52bbf9abd9c565aa72ba2fdd">
             <trusting group="com.google.protobuf"/>
             <trusting group="io.grpc"/>
@@ -448,7 +448,7 @@
             <trusting group="org.codehaus.plexus"/>
          </trusted-key>
          <trusted-key id="f3184bcd55f4d016e30d4c9bf42e87f9665015c9" group="org.jsoup"/>
-         <trusted-key id="f3d15b8ff9902805de4be6b18dc6f3d0abdbd017" group="org.codehaus.plexus" name="plexus-sec-dispatcher" version="2.0"/>
+         <trusted-key id="f3d15b8ff9902805de4be6b18dc6f3d0abdbd017" group="org.codehaus.plexus" name="plexus-sec-dispatcher" />
          <trusted-key id="f42b96b8648b5c4a1c43a62fbb2914c1fa0811c3" group="net.bytebuddy"/>
          <trusted-key id="fa1703b1d287caea3a60f931e0130a3ed5a2079e" group="org.webjars"/>
          <trusted-key id="fa77dcfef2ee6eb2debedd2c012579464d01c06a">
@@ -511,6 +511,11 @@
             <sha256 value="32001db2443b339dd21f5b79ff29d1ade722d1ba080c214bde819f0f72d1604d" origin="Generated by Gradle"/>
          </artifact>
       </component>
+      <component group="com.github.johnrengelman.shadow" name="com.github.johnrengelman.shadow.gradle.plugin" version="8.1.1">
+         <artifact name="com.github.johnrengelman.shadow.gradle.plugin-8.1.1.pom">
+            <sha256 value="3cb3886b97df6e066f108c316b219f262c97c3cb2df6da78927e645deb643cb0" origin="Generated by Gradle" reason="Artifact is not signed"/>
+         </artifact>
+      </component>
       <component group="com.google" name="google" version="1">
          <artifact name="google-1.pom">
             <sha256 value="cd6db17a11a31ede794ccbd1df0e4d9750f640234731f21cff885a9997277e81" origin="Generated by Gradle" reason="Artifact is not signed"/>
@@ -678,11 +683,6 @@
             </sha256>
          </artifact>
       </component>
-      <component group="com.github.johnrengelman.shadow" name="com.github.johnrengelman.shadow.gradle.plugin" version="8.1.1">
-         <artifact name="com.github.johnrengelman.shadow.gradle.plugin-8.1.1.pom">
-            <sha256 value="3cb3886b97df6e066f108c316b219f262c97c3cb2df6da78927e645deb643cb0" origin="Generated by Gradle" reason="Artifact is not signed"/>
-         </artifact>
-      </component>
       <component group="javax.annotation" name="jsr250-api" version="1.0">
          <artifact name="jsr250-api-1.0.jar">
             <sha256 value="a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f" origin="Generated by Gradle"/>
diff --git a/graphics/graphics-shapes/api/current.txt b/graphics/graphics-shapes/api/current.txt
index ffd01a3..ae00513 100644
--- a/graphics/graphics-shapes/api/current.txt
+++ b/graphics/graphics-shapes/api/current.txt
@@ -22,7 +22,6 @@
   }
 
   public class Cubic {
-    ctor public Cubic(float anchor0X, float anchor0Y, float control0X, float control0Y, float control1X, float control1Y, float anchor1X, float anchor1Y);
     method public static final androidx.graphics.shapes.Cubic circularArc(float centerX, float centerY, float x0, float y0, float x1, float y1);
     method public final operator androidx.graphics.shapes.Cubic div(float x);
     method public final operator androidx.graphics.shapes.Cubic div(int x);
@@ -57,6 +56,10 @@
     method public androidx.graphics.shapes.Cubic straightLine(float x0, float y0, float x1, float y1);
   }
 
+  public final class CubicKt {
+    method public static androidx.graphics.shapes.Cubic Cubic(float anchor0X, float anchor0Y, float control0X, float control0Y, float control1X, float control1Y, float anchor1X, float anchor1Y);
+  }
+
   public final class Morph {
     ctor public Morph(androidx.graphics.shapes.RoundedPolygon start, androidx.graphics.shapes.RoundedPolygon end);
     method public java.util.List<androidx.graphics.shapes.Cubic> asCubics(float progress);
diff --git a/graphics/graphics-shapes/api/restricted_current.txt b/graphics/graphics-shapes/api/restricted_current.txt
index ffd01a3..ae00513 100644
--- a/graphics/graphics-shapes/api/restricted_current.txt
+++ b/graphics/graphics-shapes/api/restricted_current.txt
@@ -22,7 +22,6 @@
   }
 
   public class Cubic {
-    ctor public Cubic(float anchor0X, float anchor0Y, float control0X, float control0Y, float control1X, float control1Y, float anchor1X, float anchor1Y);
     method public static final androidx.graphics.shapes.Cubic circularArc(float centerX, float centerY, float x0, float y0, float x1, float y1);
     method public final operator androidx.graphics.shapes.Cubic div(float x);
     method public final operator androidx.graphics.shapes.Cubic div(int x);
@@ -57,6 +56,10 @@
     method public androidx.graphics.shapes.Cubic straightLine(float x0, float y0, float x1, float y1);
   }
 
+  public final class CubicKt {
+    method public static androidx.graphics.shapes.Cubic Cubic(float anchor0X, float anchor0Y, float control0X, float control0Y, float control1X, float control1Y, float anchor1X, float anchor1Y);
+  }
+
   public final class Morph {
     ctor public Morph(androidx.graphics.shapes.RoundedPolygon start, androidx.graphics.shapes.RoundedPolygon end);
     method public java.util.List<androidx.graphics.shapes.Cubic> asCubics(float progress);
diff --git a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Cubic.kt b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Cubic.kt
index 4ab4abd..d49776a 100644
--- a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Cubic.kt
+++ b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Cubic.kt
@@ -67,38 +67,9 @@
      */
     val anchor1Y get() = points[7]
 
-    /**
-     * This class holds the anchor and control point data for a single cubic Bézier curve,
-     * with anchor points ([anchor0X], [anchor0Y]) and ([anchor1X], [anchor1Y]) at either end
-     * and control points ([control0X], [control0Y]) and ([control1X], [control1Y]) determining
-     * the slope of the curve between the anchor points.
-     *
-     * This object is immutable.
-     *
-     * @param anchor0X the first anchor point x coordinate
-     * @param anchor0Y the first anchor point y coordinate
-     * @param control0X the first control point x coordinate
-     * @param control0Y the first control point y coordinate
-     * @param control1X the second control point x coordinate
-     * @param control1Y the second control point y coordinate
-     * @param anchor1X the second anchor point x coordinate
-     * @param anchor1Y the second anchor point y coordinate
-     */
-    constructor(
-        anchor0X: Float,
-        anchor0Y: Float,
-        control0X: Float,
-        control0Y: Float,
-        control1X: Float,
-        control1Y: Float,
-        anchor1X: Float,
-        anchor1Y: Float
-    ) : this(floatArrayOf(anchor0X, anchor0Y, control0X, control0Y,
-        control1X, control1Y, anchor1X, anchor1Y))
-
     internal constructor(anchor0: Point, control0: Point, control1: Point, anchor1: Point) :
-        this(anchor0.x, anchor0.y, control0.x, control0.y,
-            control1.x, control1.y, anchor1.x, anchor1.y)
+        this(floatArrayOf(anchor0.x, anchor0.y, control0.x, control0.y,
+            control1.x, control1.y, anchor1.x, anchor1.y))
 
     /**
      * Returns a point on the curve for parameter t, representing the proportional distance
@@ -254,6 +225,35 @@
 }
 
 /**
+ * Create a Cubic that holds the anchor and control point data for a single Bézier curve,
+ * with anchor points ([anchor0X], [anchor0Y]) and ([anchor1X], [anchor1Y]) at either end
+ * and control points ([control0X], [control0Y]) and ([control1X], [control1Y]) determining
+ * the slope of the curve between the anchor points.
+ *
+ * The returned instance is immutable.
+ *
+ * @param anchor0X the first anchor point x coordinate
+ * @param anchor0Y the first anchor point y coordinate
+ * @param control0X the first control point x coordinate
+ * @param control0Y the first control point y coordinate
+ * @param control1X the second control point x coordinate
+ * @param control1Y the second control point y coordinate
+ * @param anchor1X the second anchor point x coordinate
+ * @param anchor1Y the second anchor point y coordinate
+ */
+fun Cubic(
+    anchor0X: Float,
+    anchor0Y: Float,
+    control0X: Float,
+    control0Y: Float,
+    control1X: Float,
+    control1Y: Float,
+    anchor1X: Float,
+    anchor1Y: Float
+) = Cubic(floatArrayOf(anchor0X, anchor0Y, control0X, control0Y,
+    control1X, control1Y, anchor1X, anchor1Y))
+
+/**
  * This interface is used refer to Points that can be modified, as a scope to
  * [PointTransformer]
  */
@@ -280,7 +280,6 @@
 }
 
 /**
-
  * This is a Mutable version of [Cubic], used mostly for performance critical paths so we can
  * avoid creating new [Cubic]s
  *
diff --git a/health/connect/connect-client/api/current.txt b/health/connect/connect-client/api/current.txt
index 3e9c371..7b0863a 100644
--- a/health/connect/connect-client/api/current.txt
+++ b/health/connect/connect-client/api/current.txt
@@ -9,6 +9,7 @@
     method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType, java.util.List<java.lang.String> recordIdsList, java.util.List<java.lang.String> clientRecordIdsList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? getChanges(String changesToken, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ChangesResponse>);
     method public suspend Object? getChangesToken(androidx.health.connect.client.request.ChangesTokenRequest request, kotlin.coroutines.Continuation<? super java.lang.String>);
+    method public static String getHealthConnectManageDataAction(android.content.Context context);
     method public static String getHealthConnectSettingsAction();
     method public static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context);
     method public static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional String providerPackageName);
@@ -28,6 +29,7 @@
   }
 
   public static final class HealthConnectClient.Companion {
+    method public String getHealthConnectManageDataAction(android.content.Context context);
     method public String getHealthConnectSettingsAction();
     method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context);
     method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional String providerPackageName);
diff --git a/health/connect/connect-client/api/restricted_current.txt b/health/connect/connect-client/api/restricted_current.txt
index 7bccf0e..beb3500f 100644
--- a/health/connect/connect-client/api/restricted_current.txt
+++ b/health/connect/connect-client/api/restricted_current.txt
@@ -9,6 +9,7 @@
     method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType, java.util.List<java.lang.String> recordIdsList, java.util.List<java.lang.String> clientRecordIdsList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? getChanges(String changesToken, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ChangesResponse>);
     method public suspend Object? getChangesToken(androidx.health.connect.client.request.ChangesTokenRequest request, kotlin.coroutines.Continuation<? super java.lang.String>);
+    method public static String getHealthConnectManageDataAction(android.content.Context context);
     method public static String getHealthConnectSettingsAction();
     method public static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context);
     method public static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional String providerPackageName);
@@ -28,6 +29,7 @@
   }
 
   public static final class HealthConnectClient.Companion {
+    method public String getHealthConnectManageDataAction(android.content.Context context);
     method public String getHealthConnectSettingsAction();
     method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context);
     method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional String providerPackageName);
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt
index fce572a..4f74132 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt
@@ -320,6 +320,13 @@
             "androidx.health.ACTION_HEALTH_CONNECT_SETTINGS"
 
         /**
+         * The minimum version code of the default provider APK that supports manage data intent
+         * action.
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        internal const val ACTION_MANAGE_DATA_MIN_SUPPORTED_VERSION_CODE = 82932
+
+        /**
          * Intent action to open Health Connect settings on this phone. Developers should use this
          * if they want to re-direct the user to Health Connect.
          */
@@ -461,6 +468,29 @@
             )
         }
 
+        /**
+         * Intent action to open Health Connect data management screen on this phone. Developers
+         * should use this if they want to re-direct the user to Health Connect data management.
+         *
+         * @param context the context
+         * @return Intent action to open Health Connect data management screen.
+         */
+        @JvmStatic
+        fun getHealthConnectManageDataAction(context: Context): String {
+            val action = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                "android.health.connect.action.MANAGE_HEALTH_DATA"
+            } else if (isProviderAvailable(
+                    context = context,
+                    providerVersionCode = ACTION_MANAGE_DATA_MIN_SUPPORTED_VERSION_CODE
+                )
+            ) {
+                "androidx.health.ACTION_MANAGE_HEALTH_DATA"
+            } else {
+                ACTION_HEALTH_CONNECT_SETTINGS
+            }
+            return action
+        }
+
         @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.P)
         internal fun isSdkVersionSufficient() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
 
@@ -471,11 +501,16 @@
         internal fun isProviderAvailable(
             context: Context,
             providerPackageName: String = DEFAULT_PROVIDER_PACKAGE_NAME,
+            providerVersionCode: Int = DEFAULT_PROVIDER_MIN_VERSION_CODE
         ): Boolean {
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
                 return true
             }
-            return isPackageInstalled(context.packageManager, providerPackageName)
+            return isPackageInstalled(
+                context.packageManager,
+                providerPackageName,
+                providerVersionCode
+            )
         }
 
         internal fun isProviderAvailableLegacy(
@@ -488,6 +523,7 @@
         private fun isPackageInstalled(
             packageManager: PackageManager,
             packageName: String,
+            versionCode: Int = DEFAULT_PROVIDER_MIN_VERSION_CODE
         ): Boolean {
             val packageInfo: PackageInfo =
                 try {
@@ -498,8 +534,7 @@
                 }
             return packageInfo.applicationInfo.enabled &&
                 (packageName != DEFAULT_PROVIDER_PACKAGE_NAME ||
-                    PackageInfoCompat.getLongVersionCode(packageInfo) >=
-                        DEFAULT_PROVIDER_MIN_VERSION_CODE) &&
+                    PackageInfoCompat.getLongVersionCode(packageInfo) >= versionCode) &&
                 hasBindableService(packageManager, packageName)
         }
 
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
index 2b6a910..81036fd 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
@@ -178,6 +178,45 @@
         }
     }
 
+    @Test
+    @Config(sdk = [Build.VERSION_CODES.P])
+    fun getHealthConnectManageDataAction_unsupportedClient_returnsDefaultIntent() {
+        installPackage(
+            context,
+            HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME,
+            versionCode = HealthConnectClient.DEFAULT_PROVIDER_MIN_VERSION_CODE,
+            enabled = true
+        )
+
+        assertThat(HealthConnectClient.getHealthConnectManageDataAction(context)).isEqualTo(
+            HealthConnectClient.ACTION_HEALTH_CONNECT_SETTINGS
+        )
+    }
+
+    @Test
+    @Config(sdk = [Build.VERSION_CODES.P])
+    fun getHealthConnectManageDataAction_supportedClient() {
+        installPackage(
+            context,
+            HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME,
+            versionCode = HealthConnectClient.ACTION_MANAGE_DATA_MIN_SUPPORTED_VERSION_CODE,
+            enabled = true
+        )
+        installService(context, HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME)
+
+        assertThat(HealthConnectClient.getHealthConnectManageDataAction(context)).isEqualTo(
+            "androidx.health.ACTION_MANAGE_HEALTH_DATA"
+        )
+    }
+
+    @Test
+    @Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    fun getHealthConnectManageDataAction_platformSupported() {
+        assertThat(HealthConnectClient.getHealthConnectManageDataAction(context)).isEqualTo(
+            "android.health.connect.action.MANAGE_HEALTH_DATA"
+        )
+    }
+
     private fun installPackage(
         context: Context,
         packageName: String,
diff --git a/javascriptengine/javascriptengine/api/aidlRelease/current/org/chromium/android_webview/js_sandbox/common/IJsSandboxIsolateClient.aidl b/javascriptengine/javascriptengine/api/aidlRelease/current/org/chromium/android_webview/js_sandbox/common/IJsSandboxIsolateClient.aidl
new file mode 100644
index 0000000..b00a1ca
--- /dev/null
+++ b/javascriptengine/javascriptengine/api/aidlRelease/current/org/chromium/android_webview/js_sandbox/common/IJsSandboxIsolateClient.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package org.chromium.android_webview.js_sandbox.common;
+/* @hide */
+interface IJsSandboxIsolateClient {
+  void onTerminated(int status, String message) = 1;
+  const int TERMINATE_UNKNOWN_ERROR = 1;
+  const int TERMINATE_SANDBOX_DEAD = 2;
+  const int TERMINATE_MEMORY_LIMIT_EXCEEDED = 3;
+}
diff --git a/javascriptengine/javascriptengine/api/aidlRelease/current/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl b/javascriptengine/javascriptengine/api/aidlRelease/current/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl
index cd3aae0..78a0831 100644
--- a/javascriptengine/javascriptengine/api/aidlRelease/current/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl
+++ b/javascriptengine/javascriptengine/api/aidlRelease/current/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl
@@ -37,9 +37,11 @@
   org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate createIsolate() = 0;
   List<String> getSupportedFeatures() = 1;
   org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate createIsolateWithMaxHeapSizeBytes(long maxHeapSize) = 2;
+  org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate createIsolate2(long maxHeapSize, org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolateClient isolateClient) = 3;
   const String ISOLATE_TERMINATION = "ISOLATE_TERMINATION";
   const String ISOLATE_MAX_HEAP_SIZE_LIMIT = "ISOLATE_MAX_HEAP_SIZE_LIMIT";
   const String WASM_FROM_ARRAY_BUFFER = "WASM_FROM_ARRAY_BUFFER";
   const String EVALUATE_WITHOUT_TRANSACTION_LIMIT = "EVALUATE_WITHOUT_TRANSACTION_LIMIT";
   const String CONSOLE_MESSAGING = "CONSOLE_MESSAGING";
+  const String ISOLATE_CLIENT = "ISOLATE_CLIENT";
 }
diff --git a/javascriptengine/javascriptengine/api/current.txt b/javascriptengine/javascriptengine/api/current.txt
index e523e0b..77b4206c 100644
--- a/javascriptengine/javascriptengine/api/current.txt
+++ b/javascriptengine/javascriptengine/api/current.txt
@@ -20,7 +20,7 @@
     field public static final int DEFAULT_MAX_EVALUATION_RETURN_SIZE_BYTES = 20971520; // 0x1400000
   }
 
-  public final class IsolateTerminatedException extends androidx.javascriptengine.JavaScriptException {
+  public class IsolateTerminatedException extends androidx.javascriptengine.JavaScriptException {
     ctor public IsolateTerminatedException();
   }
 
@@ -74,12 +74,12 @@
     field public static final String JS_FEATURE_WASM_COMPILATION = "JS_FEATURE_WASM_COMPILATION";
   }
 
-  public final class MemoryLimitExceededException extends androidx.javascriptengine.JavaScriptException {
+  public final class MemoryLimitExceededException extends androidx.javascriptengine.IsolateTerminatedException {
     ctor public MemoryLimitExceededException();
     ctor public MemoryLimitExceededException(String);
   }
 
-  public final class SandboxDeadException extends androidx.javascriptengine.JavaScriptException {
+  public final class SandboxDeadException extends androidx.javascriptengine.IsolateTerminatedException {
     ctor public SandboxDeadException();
   }
 
diff --git a/javascriptengine/javascriptengine/api/restricted_current.txt b/javascriptengine/javascriptengine/api/restricted_current.txt
index e523e0b..77b4206c 100644
--- a/javascriptengine/javascriptengine/api/restricted_current.txt
+++ b/javascriptengine/javascriptengine/api/restricted_current.txt
@@ -20,7 +20,7 @@
     field public static final int DEFAULT_MAX_EVALUATION_RETURN_SIZE_BYTES = 20971520; // 0x1400000
   }
 
-  public final class IsolateTerminatedException extends androidx.javascriptengine.JavaScriptException {
+  public class IsolateTerminatedException extends androidx.javascriptengine.JavaScriptException {
     ctor public IsolateTerminatedException();
   }
 
@@ -74,12 +74,12 @@
     field public static final String JS_FEATURE_WASM_COMPILATION = "JS_FEATURE_WASM_COMPILATION";
   }
 
-  public final class MemoryLimitExceededException extends androidx.javascriptengine.JavaScriptException {
+  public final class MemoryLimitExceededException extends androidx.javascriptengine.IsolateTerminatedException {
     ctor public MemoryLimitExceededException();
     ctor public MemoryLimitExceededException(String);
   }
 
-  public final class SandboxDeadException extends androidx.javascriptengine.JavaScriptException {
+  public final class SandboxDeadException extends androidx.javascriptengine.IsolateTerminatedException {
     ctor public SandboxDeadException();
   }
 
diff --git a/javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJavaScriptSandboxTest.java b/javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJavaScriptSandboxTest.java
index 8252afd..d1f5af0 100644
--- a/javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJavaScriptSandboxTest.java
+++ b/javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJavaScriptSandboxTest.java
@@ -17,12 +17,9 @@
 package androidx.javascriptengine;
 
 import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.webkit.WebView;
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
-import androidx.core.content.pm.PackageInfoCompat;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
@@ -58,18 +55,6 @@
         Assume.assumeTrue(JavaScriptSandbox.isSupported());
     }
 
-    // Get the current WebView provider version. In a versionCode of AAAABBBCD, AAAA is the build
-    // number and BBB is the patch number. C and D may usually be ignored.
-    //
-    // Strongly prefer using feature flags over version checks if possible.
-    public long getWebViewVersion() {
-        PackageInfo systemWebViewPackage = WebView.getCurrentWebViewPackage();
-        if (systemWebViewPackage == null) {
-            Assert.fail("No current WebView provider");
-        }
-        return PackageInfoCompat.getLongVersionCode(systemWebViewPackage);
-    }
-
     @Test
     @MediumTest
     public void testSimpleJsEvaluation() throws Throwable {
@@ -584,14 +569,6 @@
     @Test
     @LargeTest
     public void testHeapSizeEnforced() throws Throwable {
-        // WebView versions < 110.0.5438.0 do not contain OOM crashes to a single isolate and
-        // instead crash the whole sandbox process. This change is not tracked in a feature flag.
-        // Versions < 110.0.5438.0 are not considered to be broken, but their behavior is not
-        // of interest for this test.
-        // See Chromium change: https://chromium-review.googlesource.com/c/chromium/src/+/4047785
-        Assume.assumeTrue("WebView version does not support per-isolate OOM handling",
-                getWebViewVersion() >= 5438_000_00L);
-
         final long maxHeapSize = REASONABLE_HEAP_SIZE;
         // We need to beat the v8 optimizer to ensure it really allocates the required memory. Note
         // that we're allocating an array of elements - not bytes. Filling will ensure that the
@@ -608,9 +585,11 @@
         try (JavaScriptSandbox jsSandbox = jsSandboxFuture1.get(5, TimeUnit.SECONDS)) {
             Assume.assumeTrue(jsSandbox.isFeatureSupported(
                     JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE));
-
             Assume.assumeTrue(
                     jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
+            Assume.assumeTrue(
+                    jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_CLIENT));
+
             IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters();
             isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize);
             try (JavaScriptIsolate jsIsolate1 = jsSandbox.createIsolate(isolateStartupParameters);
@@ -625,8 +604,7 @@
                 // Wait for jsIsolate2 to fully initialize before using jsIsolate1.
                 jsIsolate2.evaluateJavaScriptAsync(stableCode).get(5, TimeUnit.SECONDS);
 
-                // Check that the heap limit is enforced and that it reports this was the evaluation
-                // that exceeded the limit.
+                // Check that the heap limit is enforced.
                 try {
                     // Use a generous timeout for OOM, as it may involve multiple rounds of garbage
                     // collection.
@@ -638,13 +616,22 @@
                     }
                 }
 
+                // Wait for termination, but don't close the isolate.
+                final CountDownLatch latch = new CountDownLatch(1);
+                jsIsolate1.addOnTerminatedCallback(Runnable::run, info -> {
+                    Assert.assertEquals(TerminationInfo.STATUS_MEMORY_LIMIT_EXCEEDED,
+                            info.getStatus());
+                    latch.countDown();
+                });
+                Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+
                 // Check that the previously submitted (but unresolved) promise evaluation reports a
                 // crash
                 try {
                     earlyUnresolvedResultFuture.get(5, TimeUnit.SECONDS);
                     Assert.fail("Should have thrown.");
                 } catch (ExecutionException e) {
-                    if (!(e.getCause() instanceof IsolateTerminatedException)) {
+                    if (!(e.getCause() instanceof MemoryLimitExceededException)) {
                         throw e;
                     }
                 }
@@ -667,11 +654,18 @@
                     }
                 }
 
-                // Check that other pre-existing isolates can still be used.
+                // Check that other pre-existing isolate in the same sandbox can no longer be used.
+                // (That the sandbox as a whole is dead.)
                 ListenableFuture<String> otherIsolateResultFuture =
                         jsIsolate2.evaluateJavaScriptAsync(stableCode);
-                String otherIsolateResult = otherIsolateResultFuture.get(5, TimeUnit.SECONDS);
-                Assert.assertEquals(stableExpected, otherIsolateResult);
+                try {
+                    otherIsolateResultFuture.get(5, TimeUnit.SECONDS);
+                    Assert.fail("Should have thrown.");
+                } catch (ExecutionException e) {
+                    if (!(e.getCause() instanceof SandboxDeadException)) {
+                        throw e;
+                    }
+                }
             }
         }
     }
@@ -679,14 +673,6 @@
     @Test
     @LargeTest
     public void testIsolateCreationAfterCrash() throws Throwable {
-        // WebView versions < 110.0.5438.0 do not contain OOM crashes to a single isolate and
-        // instead crash the whole sandbox process. This change is not tracked in a feature flag.
-        // Versions < 110.0.5438.0 are not considered to be broken, but their behavior is not
-        // of interest for this test.
-        // See Chromium change: https://chromium-review.googlesource.com/c/chromium/src/+/4047785
-        Assume.assumeTrue("WebView version does not support per-isolate OOM handling",
-                getWebViewVersion() >= 5438_000_00L);
-
         final long maxHeapSize = REASONABLE_HEAP_SIZE;
         // We need to beat the v8 optimizer to ensure it really allocates the required memory. Note
         // that we're allocating an array of elements - not bytes. Filling will ensure that the
@@ -704,14 +690,15 @@
                     JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE));
             Assume.assumeTrue(
                     jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
+            Assume.assumeTrue(
+                    jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_CLIENT));
             IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters();
             isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize);
             try (JavaScriptIsolate jsIsolate1 = jsSandbox.createIsolate(isolateStartupParameters)) {
                 ListenableFuture<String> oomResultFuture =
                         jsIsolate1.evaluateJavaScriptAsync(oomingCode);
 
-                // Check that the heap limit is enforced and that it reports this was the evaluation
-                // that exceeded the limit.
+                // Check that the heap limit is enforced.
                 try {
                     // Use a generous timeout for OOM, as it may involve multiple rounds of garbage
                     // collection.
@@ -723,28 +710,22 @@
                     }
                 }
 
-                // Check that other isolates can still be created and used (without closing
-                // jsIsolate1).
-                try (JavaScriptIsolate jsIsolate2 =
-                             jsSandbox.createIsolate(isolateStartupParameters)) {
-                    ListenableFuture<String> resultFuture =
-                            jsIsolate2.evaluateJavaScriptAsync(stableCode);
-                    String result = resultFuture.get(5, TimeUnit.SECONDS);
-                    Assert.assertEquals(stableExpected, result);
-                }
-            }
+                final CountDownLatch latch = new CountDownLatch(1);
+                jsIsolate1.addOnTerminatedCallback(Runnable::run, info -> {
+                    Assert.assertEquals(TerminationInfo.STATUS_MEMORY_LIMIT_EXCEEDED,
+                            info.getStatus());
+                    latch.countDown();
+                });
+                Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
 
-            // Check that other isolates can still be created and used (after closing jsIsolate1).
-            try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) {
-                ListenableFuture<String> resultFuture =
-                        jsIsolate.evaluateJavaScriptAsync(stableCode);
-                String result = resultFuture.get(5, TimeUnit.SECONDS);
-                Assert.assertEquals(stableExpected, result);
+                // Check that new isolates can no longer be created in the same sandbox.
+                Assert.assertThrows(IllegalStateException.class,
+                        () -> jsSandbox.createIsolate(isolateStartupParameters));
             }
         }
 
-        // Check that the old sandbox with the "crashed" isolate can be torn down and that a new
-        // sandbox and isolate can be spun up.
+        // Check that after the old OOMed sandbox is closed and torn down that a new sandbox and
+        // isolate can be spun up.
         ListenableFuture<JavaScriptSandbox> jsSandboxFuture2 =
                 JavaScriptSandbox.createConnectedInstanceAsync(context);
         try (JavaScriptSandbox jsSandbox = jsSandboxFuture2.get(5, TimeUnit.SECONDS);
@@ -1144,4 +1125,80 @@
             Assert.assertEquals(expected, result);
         }
     }
+
+    @Test
+    @LargeTest
+    public void testTerminationNotificationForSandboxDeath() throws Throwable {
+        final Context context = ApplicationProvider.getApplicationContext();
+        final ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+                JavaScriptSandbox.createConnectedInstanceAsync(context);
+        try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
+            try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
+                final ListenableFuture<String> loopFuture =
+                        jsIsolate.evaluateJavaScriptAsync("while(true);");
+
+                final CountDownLatch latch = new CountDownLatch(2);
+
+                final Runnable futureCallback = () -> {
+                    try {
+                        loopFuture.get();
+                        Assert.fail("Should have thrown.");
+                    } catch (ExecutionException e) {
+                        if (!(e.getCause() instanceof SandboxDeadException)) {
+                            Assert.fail("Wrong exception for evaluation: " + e);
+                        }
+                    } catch (InterruptedException e) {
+                        Assert.fail("Interrupted: " + e);
+                    }
+                    latch.countDown();
+                };
+                loopFuture.addListener(futureCallback, Runnable::run);
+
+                jsIsolate.addOnTerminatedCallback(Runnable::run, info -> {
+                    Assert.assertEquals(TerminationInfo.STATUS_SANDBOX_DEAD, info.getStatus());
+                    latch.countDown();
+                });
+
+                jsSandbox.close();
+
+                Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+            }
+        }
+    }
+
+    @Test
+    @LargeTest
+    public void testOomOutsideOfEvaluation() throws Throwable {
+        final Context context = ApplicationProvider.getApplicationContext();
+        final ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
+                JavaScriptSandbox.createConnectedInstanceAsync(context);
+        try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(
+                    JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE));
+            Assume.assumeTrue(
+                    jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_CLIENT));
+            IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters();
+            isolateStartupParameters.setMaxHeapSizeBytes(REASONABLE_HEAP_SIZE);
+            try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) {
+                // OOM should occur in a microtask, not during this evaluation, so we should
+                // never get a MemoryLimitExceededException from the evaluation future.
+                final String code = ""
+                        + "const bytes = [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];"
+                        + "WebAssembly.compile(new Uint8Array(bytes)).then(() => {"
+                        + "  this.array ="
+                        + "    Array(" + REASONABLE_HEAP_SIZE + ").fill(Math.random(), 0);"
+                        + "});"
+                        + "'PASS'";
+                jsIsolate.evaluateJavaScriptAsync(code).get(5, TimeUnit.SECONDS);
+
+                final CountDownLatch latch = new CountDownLatch(1);
+                jsIsolate.addOnTerminatedCallback(Runnable::run, info -> {
+                    Assert.assertEquals(
+                            TerminationInfo.STATUS_MEMORY_LIMIT_EXCEEDED, info.getStatus());
+                    latch.countDown();
+                });
+                Assert.assertTrue(latch.await(60, TimeUnit.SECONDS));
+            }
+        }
+    }
 }
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/EnvironmentDeadState.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/EnvironmentDeadState.java
index 0c99deb..c10d56fd 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/EnvironmentDeadState.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/EnvironmentDeadState.java
@@ -18,6 +18,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.core.util.Consumer;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -25,15 +26,19 @@
 
 /**
  * Covers the case where the environment is dead.
- *
+ * <p>
  * This state covers cases where the developer explicitly closes the sandbox or sandbox/isolate
  * being dead outside of the control of the developer.
+ * <p>
+ * Although being in this state is considered terminated from the app perspective, the service
+ * side may still technically be running.
  */
 final class EnvironmentDeadState implements IsolateState {
-    private final JavaScriptException mException;
+    @NonNull
+    private final TerminationInfo mTerminationInfo;
 
-    EnvironmentDeadState(JavaScriptException e) {
-        mException = e;
+    EnvironmentDeadState(@NonNull TerminationInfo terminationInfo) {
+        mTerminationInfo = terminationInfo;
     }
 
     @NonNull
@@ -41,7 +46,7 @@
     public ListenableFuture<String> evaluateJavaScriptAsync(@NonNull String code) {
         return CallbackToFutureAdapter.getFuture(completer -> {
             final String futureDebugMessage = "evaluateJavascript Future";
-            completer.setException(mException);
+            completer.setException(mTerminationInfo.toJavaScriptException());
             return futureDebugMessage;
         });
     }
@@ -69,12 +74,16 @@
     }
 
     @Override
-    public IsolateState setSandboxDead() {
-        return new EnvironmentDeadState(new SandboxDeadException());
+    public boolean canDie() {
+        return false;
     }
 
     @Override
-    public IsolateState setIsolateDead() {
-        return this;
+    public void addOnTerminatedCallback(@NonNull Executor executor,
+            @NonNull Consumer<TerminationInfo> callback) {
+        executor.execute(() -> callback.accept(mTerminationInfo));
     }
+
+    @Override
+    public void removeOnTerminatedCallback(@NonNull Consumer<TerminationInfo> callback) {}
 }
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateClosedState.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateClosedState.java
index 927bbe0..ba944b1 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateClosedState.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateClosedState.java
@@ -17,48 +17,55 @@
 package androidx.javascriptengine;
 
 import androidx.annotation.NonNull;
+import androidx.core.util.Consumer;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
 import java.util.concurrent.Executor;
 
 /**
- * Covers the case where the isolate is explicitly closed by the developer.
+ * Covers cases where the isolate is explicitly closed or uninitialized.
+ * <p>
+ * Although being in this state is considered terminated from the app perspective, the service
+ * side may still technically be running.
  */
 final class IsolateClosedState implements IsolateState {
-    IsolateClosedState() {
+    @NonNull
+    private final String mDescription;
+    IsolateClosedState(@NonNull String description) {
+        mDescription = description;
     }
 
     @NonNull
     @Override
     public ListenableFuture<String> evaluateJavaScriptAsync(@NonNull String code) {
-        throw new IllegalStateException("Calling evaluateJavaScriptAsync() after closing the"
-                + "Isolate");
+        throw new IllegalStateException(
+                "Calling evaluateJavaScriptAsync() when " + mDescription);
     }
 
     @Override
     public void setConsoleCallback(@NonNull Executor executor,
             @NonNull JavaScriptConsoleCallback callback) {
         throw new IllegalStateException(
-                "Calling setConsoleCallback() after closing the Isolate");
+                "Calling setConsoleCallback() when " + mDescription);
     }
 
     @Override
     public void setConsoleCallback(@NonNull JavaScriptConsoleCallback callback) {
         throw new IllegalStateException(
-                "Calling setConsoleCallback() after closing the Isolate");
+                "Calling setConsoleCallback() when " + mDescription);
     }
 
     @Override
     public void clearConsoleCallback() {
         throw new IllegalStateException(
-                "Calling clearConsoleCallback() after closing the Isolate");
+                "Calling clearConsoleCallback() when " + mDescription);
     }
 
     @Override
     public boolean provideNamedData(@NonNull String name, @NonNull byte[] inputBytes) {
         throw new IllegalStateException(
-                "Calling provideNamedData() after closing the Isolate");
+                "Calling provideNamedData() when " + mDescription);
     }
 
     @Override
@@ -66,12 +73,20 @@
     }
 
     @Override
-    public IsolateState setSandboxDead() {
-        return this;
+    public boolean canDie() {
+        return false;
     }
 
     @Override
-    public IsolateState setIsolateDead() {
-        return this;
+    public void addOnTerminatedCallback(@NonNull Executor executor,
+            @NonNull Consumer<TerminationInfo> callback) {
+        throw new IllegalStateException(
+                "Calling addOnTerminatedCallback() when " + mDescription);
+    }
+
+    @Override
+    public void removeOnTerminatedCallback(@NonNull Consumer<TerminationInfo> callback) {
+        throw new IllegalStateException(
+                "Calling removeOnTerminatedCallback() when " + mDescription);
     }
 }
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateStartupParameters.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateStartupParameters.java
index a1f5013..fe7714f 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateStartupParameters.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateStartupParameters.java
@@ -47,10 +47,17 @@
      * some multiple of bytes, be increased to some minimum value, or reduced to some maximum
      * supported value.
      * <p>
-     * Exceeding this limit will usually result in a {@link MemoryLimitExceededException},
-     * but beware that not all JavaScript sandbox service implementations (particularly older ones)
-     * handle memory exhaustion equally gracefully, and may crash the entire sandbox (see
-     * {@link SandboxDeadException}).
+     * Exceeding this limit will usually result in all unfinished and future evaluations failing
+     * with {@link MemoryLimitExceededException} and the isolate terminating with a status of
+     * {@link TerminationInfo#STATUS_MEMORY_LIMIT_EXCEEDED}. Note that exceeding the memory limit
+     * will take down the entire sandbox - not just the responsible isolate - and all other
+     * isolates will receive generic {@link SandboxDeadException} and
+     * {@link TerminationInfo#STATUS_SANDBOX_DEAD} errors.
+     * <p>
+     * Not all JavaScript sandbox service implementations (particularly older ones) handle memory
+     * exhaustion equally, and may crash the sandbox without attributing the failure to memory
+     * exhaustion in a particular isolate.
+     *
      * @param size heap size in bytes
      */
     @RequiresFeature(name = JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE,
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateState.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateState.java
index 5a44f48..6188bc9 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateState.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateState.java
@@ -17,6 +17,7 @@
 package androidx.javascriptengine;
 
 import androidx.annotation.NonNull;
+import androidx.core.util.Consumer;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -24,17 +25,16 @@
 
 /**
  * Interface for State design pattern.
- *
+ * <p>
  * Isolates can be in different states due to events within/outside the control of the developer.
  * This pattern allows us to extract out the state related behaviour without maintaining it all in
  * the JavaScriptIsolate class which proved to be error-prone and hard to read.
- *
+ * <p>
  * State specific behaviour are implemented in concrete classes that implements this interface.
- *
+ * <p>
  * Refer: https://en.wikipedia.org/wiki/State_pattern
  */
 interface IsolateState {
-
     @NonNull
     ListenableFuture<String> evaluateJavaScriptAsync(@NonNull String code);
 
@@ -49,7 +49,22 @@
 
     void close();
 
-    IsolateState setIsolateDead();
+    /**
+     * Check whether the current state is permitted to transition to a dead state
+     *
+     * @return true iff a transition to a dead state is permitted.
+     */
+    boolean canDie();
 
-    IsolateState setSandboxDead();
+    /**
+     * Method to run after this state has been replaced by a dead state.
+     *
+     * @param terminationInfo The termination info describing the death.
+     */
+    default void onDied(@NonNull TerminationInfo terminationInfo) {}
+
+    void addOnTerminatedCallback(@NonNull Executor executor,
+            @NonNull Consumer<TerminationInfo> callback);
+
+    void removeOnTerminatedCallback(@NonNull Consumer<TerminationInfo> callback);
 }
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateTerminatedException.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateTerminatedException.java
index b7547ecb..8548c19 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateTerminatedException.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateTerminatedException.java
@@ -16,27 +16,42 @@
 
 package androidx.javascriptengine;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.core.util.Consumer;
+
+import java.util.concurrent.Executor;
+
 /**
  * Exception thrown when evaluation is terminated due to the {@link JavaScriptIsolate} being closed
- * or crashing.
+ * or due to some crash.
  * <p>
  * Calling {@link JavaScriptIsolate#close()} will cause this exception to be thrown for all
  * previously requested but pending evaluations.
  * <p>
- * If the individual isolate has crashed, for example, due to exceeding a memory limit, this
- * exception will also be thrown for all pending and future evaluations (until
- * {@link JavaScriptIsolate#close()} is called).
- * <p>
- * Note that if the sandbox as a whole has crashed or been closed, {@link SandboxDeadException} will
- * be thrown instead.
+ * If an isolate has crashed (but not been closed), subsequently requested evaluations will fail
+ * immediately with an IsolateTerminatedException (or a subclass) consistent with that
+ * used for evaluations submitted before the crash.
  * <p>
  * Note that this exception will not be thrown if the isolate has been explicitly closed before a
  * call to {@link JavaScriptIsolate#evaluateJavaScriptAsync(String)}, which will instead immediately
  * throw an IllegalStateException (and not asynchronously via a future). This applies even if the
  * isolate was closed following a crash.
+ * <p>
+ * Do not attempt to parse the information in this exception's message as it may change between
+ * JavaScriptEngine versions.
+ * <p>
+ * Note that it is possible for an isolate to crash outside of submitted evaluations, in which
+ * case an IsolateTerminatedException may not be observed. Consider instead using
+ * {@link JavaScriptIsolate#addOnTerminatedCallback(Executor, Consumer)} if you need to reliably
+ * or immediately detect isolate crashes rather than evaluation failures.
  */
-public final class IsolateTerminatedException extends JavaScriptException {
+public class IsolateTerminatedException extends JavaScriptException {
     public IsolateTerminatedException() {
         super();
     }
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public IsolateTerminatedException(@NonNull String message) {
+        super(message);
+    }
 }
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateUsableState.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateUsableState.java
index e253c86..8f7f2f9 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateUsableState.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/IsolateUsableState.java
@@ -24,6 +24,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.core.util.Consumer;
 import androidx.javascriptengine.common.LengthLimitExceededException;
 import androidx.javascriptengine.common.Utils;
 
@@ -37,6 +38,7 @@
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
@@ -65,6 +67,11 @@
     @GuardedBy("mLock")
     private Set<CallbackToFutureAdapter.Completer<String>> mPendingCompleterSet =
             new HashSet<>();
+    // mOnTerminatedCallbacks does not require this.mLock, as all accesses should be performed
+    // whilst holding the mLock of the JavaScriptIsolate that owns this state object.
+    @NonNull
+    private final HashMap<Consumer<TerminationInfo>, Executor> mOnTerminatedCallbacks =
+            new HashMap<>();
 
     private class IJsSandboxIsolateSyncCallbackStubWrapper extends
             IJsSandboxIsolateSyncCallback.Stub {
@@ -79,6 +86,9 @@
         @Override
         public void reportResultWithFd(AssetFileDescriptor afd) {
             Objects.requireNonNull(afd);
+            // The completer needs to be removed before offloading to the executor, otherwise there
+            // is a race to complete it if all evaluations are cancelled.
+            removePending(mCompleter);
             mJsIsolate.mJsSandbox.mThreadPoolTaskExecutor.execute(
                     () -> {
                         String result;
@@ -87,13 +97,11 @@
                                     mMaxEvaluationReturnSizeBytes,
                                     /*truncate=*/false);
                         } catch (IOException | UnsupportedOperationException ex) {
-                            removePending(mCompleter);
                             mCompleter.setException(
                                     new JavaScriptException(
                                             "Retrieving result failed: " + ex.getMessage()));
                             return;
                         } catch (LengthLimitExceededException ex) {
-                            removePending(mCompleter);
                             if (ex.getMessage() != null) {
                                 mCompleter.setException(
                                         new EvaluationResultSizeLimitExceededException(
@@ -111,6 +119,9 @@
         @Override
         public void reportErrorWithFd(@ExecutionErrorTypes int type, AssetFileDescriptor afd) {
             Objects.requireNonNull(afd);
+            // The completer needs to be removed before offloading to the executor, otherwise there
+            // is a race to complete it if all evaluations are cancelled.
+            removePending(mCompleter);
             mJsIsolate.mJsSandbox.mThreadPoolTaskExecutor.execute(
                     () -> {
                         String error;
@@ -119,7 +130,6 @@
                                     mMaxEvaluationReturnSizeBytes,
                                     /*truncate=*/true);
                         } catch (IOException | UnsupportedOperationException ex) {
-                            removePending(mCompleter);
                             mCompleter.setException(
                                     new JavaScriptException(
                                             "Retrieving error failed: " + ex.getMessage()));
@@ -144,6 +154,7 @@
         @Override
         public void reportResult(String result) {
             Objects.requireNonNull(result);
+            removePending(mCompleter);
             final long identityToken = Binder.clearCallingIdentity();
             try {
                 handleEvaluationResult(mCompleter, result);
@@ -155,6 +166,7 @@
         @Override
         public void reportError(@ExecutionErrorTypes int type, String error) {
             Objects.requireNonNull(error);
+            removePending(mCompleter);
             final long identityToken = Binder.clearCallingIdentity();
             try {
                 handleEvaluationError(mCompleter, type, error);
@@ -239,11 +251,12 @@
                     new IJsSandboxIsolateCallbackStubWrapper(completer);
             try {
                 mJsIsolateStub.evaluateJavascript(code, callbackStub);
-                addToPendingCompleterSet(completer);
+                addPending(completer);
             } catch (DeadObjectException e) {
                 // The sandbox process has died.
-                mJsIsolate.maybeSetSandboxDead();
-                completer.setException(new SandboxDeadException());
+                final TerminationInfo terminationInfo = mJsIsolate.maybeSetSandboxDead();
+                Objects.requireNonNull(terminationInfo);
+                completer.setException(terminationInfo.toJavaScriptException());
             } catch (RemoteException e) {
                 completer.setException(new RuntimeException(e));
             }
@@ -309,49 +322,52 @@
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException was thrown during close()", e);
         }
-        cancelAllPendingEvaluations(new IsolateTerminatedException());
+        cancelAllPendingEvaluations(new IsolateTerminatedException("isolate closed"));
     }
 
     @Override
-    public IsolateState setIsolateDead() {
-        IsolateTerminatedException exception = new IsolateTerminatedException();
-        cancelAllPendingEvaluations(exception);
-        return new EnvironmentDeadState(exception);
+    public boolean canDie() {
+        return true;
     }
 
     @Override
-    public IsolateState setSandboxDead() {
-        SandboxDeadException exception = new SandboxDeadException();
-        cancelAllPendingEvaluations(exception);
-        return new EnvironmentDeadState(exception);
+    public void onDied(@NonNull TerminationInfo terminationInfo) {
+        cancelAllPendingEvaluations(terminationInfo.toJavaScriptException());
+        mOnTerminatedCallbacks.forEach(
+                (callback, executor) -> executor.execute(() -> callback.accept(terminationInfo)));
     }
 
+    // Caller should call mJsIsolate.removePending(mCompleter) first
     void handleEvaluationError(@NonNull CallbackToFutureAdapter.Completer<String> completer,
             int type, @NonNull String error) {
-        removePending(completer);
-        boolean crashing = false;
         switch (type) {
             case IJsSandboxIsolateSyncCallback.JS_EVALUATION_ERROR:
                 completer.setException(new EvaluationFailedException(error));
                 break;
             case IJsSandboxIsolateSyncCallback.MEMORY_LIMIT_EXCEEDED:
-                completer.setException(new MemoryLimitExceededException(error));
-                crashing = true;
+                // Note that we won't ever receive a MEMORY_LIMIT_EXCEEDED evaluation error if
+                // the service side supports termination notifications, so this only handles the
+                // case where it doesn't.
+                final TerminationInfo terminationInfo =
+                        new TerminationInfo(TerminationInfo.STATUS_MEMORY_LIMIT_EXCEEDED, error);
+                mJsIsolate.maybeSetIsolateDead(terminationInfo);
+                // The completer was already removed from the set, so we're responsible for it.
+                // Use our exception even if the isolate was already dead or closed. This might
+                // result in an exception which is inconsistent with everything else if there was
+                // a death or close before we called maybeSetIsolateDead above, but that requires
+                // the app to have already set up a race condition.
+                completer.setException(terminationInfo.toJavaScriptException());
                 break;
             default:
                 completer.setException(new JavaScriptException(
-                        "Crashing due to unknown JavaScriptException: " + error));
-                // Assume the worst
-                crashing = true;
-        }
-        if (crashing) {
-            mJsIsolate.maybeSetIsolateDead();
+                        "Unknown error: code " + type + ": " + error));
+                break;
         }
     }
 
+    // Caller should call mJsIsolate.removePending(mCompleter) first
     void handleEvaluationResult(@NonNull CallbackToFutureAdapter.Completer<String> completer,
             @NonNull String result) {
-        removePending(completer);
         completer.set(result);
     }
 
@@ -361,7 +377,7 @@
         }
     }
 
-    void addToPendingCompleterSet(@NonNull CallbackToFutureAdapter.Completer<String> completer) {
+    void addPending(@NonNull CallbackToFutureAdapter.Completer<String> completer) {
         synchronized (mLock) {
             mPendingCompleterSet.add(completer);
         }
@@ -394,11 +410,12 @@
                     mJsIsolateStub.evaluateJavascriptWithFd(codeAfd,
                             callbackStub);
                 }
-                addToPendingCompleterSet(completer);
+                addPending(completer);
             } catch (DeadObjectException e) {
                 // The sandbox process has died.
-                mJsIsolate.maybeSetSandboxDead();
-                completer.setException(new SandboxDeadException());
+                final TerminationInfo terminationInfo = mJsIsolate.maybeSetSandboxDead();
+                Objects.requireNonNull(terminationInfo);
+                completer.setException(terminationInfo.toJavaScriptException());
             } catch (RemoteException | IOException e) {
                 completer.setException(new RuntimeException(e));
             }
@@ -406,4 +423,19 @@
             return futureDebugMessage;
         });
     }
+
+    @Override
+    public void addOnTerminatedCallback(@NonNull Executor executor,
+            @NonNull Consumer<TerminationInfo> callback) {
+        if (mOnTerminatedCallbacks.putIfAbsent(callback, executor) != null) {
+            throw new IllegalStateException("Termination callback already registered");
+        }
+    }
+
+    @Override
+    public void removeOnTerminatedCallback(@NonNull Consumer<TerminationInfo> callback) {
+        synchronized (mLock) {
+            mOnTerminatedCallbacks.remove(callback);
+        }
+    }
 }
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptIsolate.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptIsolate.java
index df63307..ab19538 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptIsolate.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptIsolate.java
@@ -16,12 +16,20 @@
 
 package androidx.javascriptengine;
 
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresFeature;
+import androidx.annotation.RestrictTo;
+import androidx.core.util.Consumer;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
 import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate;
+import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolateClient;
 
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -49,44 +57,106 @@
     private final Object mLock = new Object();
     private final CloseGuardHelper mGuard = CloseGuardHelper.create();
 
+    @NonNull
     final JavaScriptSandbox mJsSandbox;
 
     @GuardedBy("mLock")
     @NonNull
     private IsolateState mIsolateState;
 
-    JavaScriptIsolate(@NonNull IJsSandboxIsolate jsIsolateStub, @NonNull JavaScriptSandbox sandbox,
-            @NonNull IsolateStartupParameters settings) {
+    private final class JsSandboxIsolateClient extends IJsSandboxIsolateClient.Stub {
+        JsSandboxIsolateClient() {}
+
+        @Override
+        public void onTerminated(int status, String message) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                // If we're already closed, this will do nothing
+                maybeSetIsolateDead(new TerminationInfo(status, message));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    @NonNull
+    static JavaScriptIsolate create(@NonNull JavaScriptSandbox sandbox,
+            IsolateStartupParameters settings) throws RemoteException {
+        final JavaScriptIsolate isolate = new JavaScriptIsolate(sandbox);
+        isolate.initialize(settings);
+        return isolate;
+    }
+
+    private JavaScriptIsolate(@NonNull JavaScriptSandbox sandbox) {
+        mJsSandbox = sandbox;
         synchronized (mLock) {
+            mIsolateState = new IsolateClosedState("isolate not initialized");
+        }
+    }
+
+    // Create an isolate on the service side and complete initialization.
+    // This is done outside of the constructor to avoid leaking a partially constructed
+    // JavaScriptIsolate to the service (which would complicate thread-safety).
+    private void initialize(@NonNull IsolateStartupParameters settings) throws RemoteException {
+        synchronized (mLock) {
+            final IJsSandboxIsolateClient instanceCallback;
+            if (mJsSandbox.isFeatureSupported(
+                    JavaScriptSandbox.JS_FEATURE_ISOLATE_CLIENT)) {
+                instanceCallback = new JsSandboxIsolateClient();
+            } else {
+                instanceCallback = null;
+            }
+            IJsSandboxIsolate jsIsolateStub = mJsSandbox.createIsolateOnService(settings,
+                    instanceCallback);
             mIsolateState = new IsolateUsableState(this, jsIsolateStub,
                     settings.getMaxEvaluationReturnSizeBytes());
+            mGuard.open("close");
         }
-        mJsSandbox = sandbox;
-        mGuard.open("close");
-        // This should be at the end of the constructor.
     }
 
     /**
      * Changes the state to denote that the isolate is dead.
-     *
+     * <p>
      * {@link IsolateClosedState} takes precedence so it will not change state if the current state
-     * is {@link IsolateClosedState}
+     * is {@link IsolateClosedState}.
+     * <p>
+     * If the isolate is already dead, the existing dead state is preserved.
+     *
+     * @return true iff the state was changed to a new EnvironmentDeadState
      */
-    void maybeSetIsolateDead() {
+    boolean maybeSetIsolateDead(@NonNull TerminationInfo terminationInfo) {
         synchronized (mLock) {
-            mIsolateState = mIsolateState.setIsolateDead();
+            if (terminationInfo.getStatus() == TerminationInfo.STATUS_MEMORY_LIMIT_EXCEEDED) {
+                Log.e(TAG, "isolate exceeded its heap memory limit - killing sandbox");
+                mJsSandbox.kill();
+            }
+            final IsolateState oldState = mIsolateState;
+            if (oldState.canDie()) {
+                mIsolateState = new EnvironmentDeadState(terminationInfo);
+                oldState.onDied(terminationInfo);
+                return true;
+            }
         }
+        return false;
     }
 
     /**
      * Changes the state to denote that the sandbox is dead.
+     * <p>
+     * See {@link #maybeSetIsolateDead(TerminationInfo)} for additional information.
      *
-     * {@link IsolateClosedState} takes precedence so it will not change state if the current state
-     * is {@link IsolateClosedState}
+     * @return the generated termination info if it was set, or null if the state did not change.
      */
-    void maybeSetSandboxDead() {
+    @Nullable
+    TerminationInfo maybeSetSandboxDead() {
         synchronized (mLock) {
-            mIsolateState = mIsolateState.setSandboxDead();
+            final TerminationInfo terminationInfo =
+                    new TerminationInfo(TerminationInfo.STATUS_SANDBOX_DEAD, "sandbox dead");
+            if (maybeSetIsolateDead(terminationInfo)) {
+                return terminationInfo;
+            } else {
+                return null;
+            }
         }
     }
 
@@ -143,23 +213,28 @@
     /**
      * Closes the {@link JavaScriptIsolate} object and renders it unusable.
      * <p>
-     * Once closed, no more method calls should be made. Pending evaluations resolve with
-     * {@link IsolateTerminatedException} immediately.
+     * Once closed, no more method calls should be made. Pending evaluations will reject with
+     * an {@link IsolateTerminatedException} immediately.
      * <p>
      * If {@link JavaScriptSandbox#isFeatureSupported(String)} is {@code true} for {@link
-     * JavaScriptSandbox#JS_FEATURE_ISOLATE_TERMINATION}, then any pending evaluation is immediately
-     * terminated and memory is freed. If it is {@code false}, the isolate will not get cleaned
+     * JavaScriptSandbox#JS_FEATURE_ISOLATE_TERMINATION}, then any pending evaluations are
+     * terminated. If it is {@code false}, the isolate will not get cleaned
      * up until the pending evaluations have run to completion and will consume resources until
      * then.
+     * <p>
+     * Closing an isolate via this method does not wait on the isolate to clean up. Resources
+     * held by the isolate may remain in use for a duration after this method returns.
      */
     @Override
     public void close() {
         synchronized (mLock) {
             mIsolateState.close();
-            mIsolateState = new IsolateClosedState();
-            mJsSandbox.removeFromIsolateSet(this);
-            mGuard.close();
+            mIsolateState = new IsolateClosedState("isolate closed");
         }
+        // Do not hold mLock whilst calling into JavaScriptSandbox, as JavaScriptSandbox also has
+        // its own lock and may want to call into JavaScriptIsolate from another thread.
+        mJsSandbox.removeFromIsolateSet(this);
+        mGuard.close();
     }
 
     /**
@@ -296,4 +371,58 @@
             mIsolateState.clearConsoleCallback();
         }
     }
+
+    /**
+     * Add a callback to listen for isolate crashes.
+     * <p>
+     * There is no guaranteed order to when these callbacks are triggered and unfinished
+     * evaluations' futures are rejected.
+     * <p>
+     * Registering a callback after the isolate has crashed will result in it being executed
+     * immediately on the supplied executor with the isolate's {@link TerminationInfo} as an
+     * argument.
+     * <p>
+     * Closing an isolate via {@link #close()} is not considered a crash, even if there are
+     * unresolved evaluations, and will not trigger termination callbacks.
+     *
+     * @param executor Executor with which to run callback.
+     * @param callback Consumer to be called with TerminationInfo when a crash occurs.
+     * @throws IllegalStateException if the callback is already registered (using any executor).
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public void addOnTerminatedCallback(@NonNull Executor executor,
+            @NonNull Consumer<TerminationInfo> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        synchronized (mLock) {
+            mIsolateState.addOnTerminatedCallback(executor, callback);
+        }
+    }
+
+    /**
+     * Add a callback to listen for isolate crashes.
+     * <p>
+     * This is the same as calling {@link #addOnTerminatedCallback(Executor, Consumer)} using the
+     * main executor of the context used to create the {@link JavaScriptSandbox} object.
+     *
+     * @param callback Consumer to be called with TerminationInfo when a crash occurs.
+     * @throws IllegalStateException if the callback is already registered (using any executor).
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public void addOnTerminatedCallback(@NonNull Consumer<TerminationInfo> callback) {
+        addOnTerminatedCallback(mJsSandbox.getMainExecutor(), callback);
+    }
+
+    /**
+     * Remove a callback previously registered with addOnTerminatedCallback.
+     *
+     * @param callback The callback to unregister, if currently registered.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public void removeOnTerminatedCallback(@NonNull Consumer<TerminationInfo> callback) {
+        Objects.requireNonNull(callback);
+        synchronized (mLock) {
+            mIsolateState.removeOnTerminatedCallback(callback);
+        }
+    }
 }
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptSandbox.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptSandbox.java
index 834502a..8a5c048 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptSandbox.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptSandbox.java
@@ -37,21 +37,25 @@
 import com.google.common.util.concurrent.ListenableFuture;
 
 import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate;
+import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolateClient;
 import org.chromium.android_webview.js_sandbox.common.IJsSandboxService;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 
 import javax.annotation.concurrent.GuardedBy;
 
@@ -88,14 +92,24 @@
     private final Object mLock = new Object();
     private final CloseGuardHelper mGuard = CloseGuardHelper.create();
 
+    // This is null iff the sandbox is closed.
     @Nullable
     @GuardedBy("mLock")
     private IJsSandboxService mJsSandboxService;
 
-    private final ConnectionSetup mConnection;
+    // Don't use mLock for the connection, allowing it to be severed at any time, regardless of
+    // the status of the main mLock. Use an AtomicReference instead.
+    //
+    // The underlying ConnectionSetup is nullable, and is null iff the service has been unbound
+    // (which should also imply dead or closed).
+    @NonNull
+    private final AtomicReference<ConnectionSetup> mConnection;
+    @NonNull
+    private final Context mContext;
 
     @GuardedBy("mLock")
-    private final HashSet<JavaScriptIsolate> mActiveIsolateSet = new HashSet<>();
+    @NonNull
+    private Set<JavaScriptIsolate> mActiveIsolateSet;
 
     final ExecutorService mThreadPoolTaskExecutor =
             Executors.newCachedThreadPool(new ThreadFactory() {
@@ -120,6 +134,7 @@
                     JS_FEATURE_ISOLATE_MAX_HEAP_SIZE,
                     JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT,
                     JS_FEATURE_CONSOLE_MESSAGING,
+                    JS_FEATURE_ISOLATE_CLIENT,
             })
     @Retention(RetentionPolicy.SOURCE)
     @Target({ElementType.PARAMETER, ElementType.METHOD})
@@ -198,6 +213,15 @@
      */
     public static final String JS_FEATURE_CONSOLE_MESSAGING = "JS_FEATURE_CONSOLE_MESSAGING";
 
+    /**
+     * Feature for {@link #isFeatureSupported(String)}.
+     * <p>
+     * When this feature is present, the service can be provided with a Binder interface for
+     * calling into the client, independent of callbacks.
+     */
+    static final String JS_FEATURE_ISOLATE_CLIENT =
+            "JS_FEATURE_ISOLATE_CLIENT";
+
     // This set must not be modified after JavaScriptSandbox construction.
     @NonNull
     private final HashSet<String> mClientSideFeatureSet;
@@ -207,7 +231,8 @@
         private CallbackToFutureAdapter.Completer<JavaScriptSandbox> mCompleter;
         @Nullable
         private JavaScriptSandbox mJsSandbox;
-        final Context mContext;
+        @NonNull
+        private final Context mContext;
 
         @Override
         @SuppressWarnings("NullAway")
@@ -222,7 +247,7 @@
             IJsSandboxService jsSandboxService =
                     IJsSandboxService.Stub.asInterface(service);
             try {
-                mJsSandbox = new JavaScriptSandbox(this, jsSandboxService);
+                mJsSandbox = new JavaScriptSandbox(mContext, this, jsSandboxService);
             } catch (RemoteException e) {
                 runShutdownTasks(e);
                 return;
@@ -386,12 +411,14 @@
 
     // We prevent direct initializations of this class.
     // Use JavaScriptSandbox.createConnectedInstance().
-    JavaScriptSandbox(@NonNull ConnectionSetup connectionSetup,
+    JavaScriptSandbox(@NonNull Context context, @NonNull ConnectionSetup connectionSetup,
             @NonNull IJsSandboxService jsSandboxService) throws RemoteException {
-        mConnection = connectionSetup;
+        mContext = context;
+        mConnection = new AtomicReference<>(connectionSetup);
         mJsSandboxService = jsSandboxService;
         final List<String> features = mJsSandboxService.getSupportedFeatures();
         mClientSideFeatureSet = buildClientSideFeatureSet(features);
+        mActiveIsolateSet = new HashSet<>();
         mGuard.open("close");
         // This should be at the end of the constructor.
     }
@@ -415,27 +442,39 @@
     public JavaScriptIsolate createIsolate(@NonNull IsolateStartupParameters settings) {
         Objects.requireNonNull(settings);
         synchronized (mLock) {
-            if (mJsSandboxService == null) {
+            // TODO(b/290750354, b/290749782): Implement separate closed vs dead state and use
+            //  that instead of a mConnection nullness check.
+            if (mJsSandboxService == null || mConnection.get() == null) {
                 throw new IllegalStateException(
                         "Attempting to createIsolate on a service that isn't connected");
             }
-            IJsSandboxIsolate isolateStub;
+            final JavaScriptIsolate isolate;
             try {
-                if (settings.getMaxHeapSizeBytes()
-                        == IsolateStartupParameters.DEFAULT_ISOLATE_HEAP_SIZE) {
-                    isolateStub = mJsSandboxService.createIsolate();
-                } else {
-                    isolateStub = mJsSandboxService.createIsolateWithMaxHeapSizeBytes(
-                            settings.getMaxHeapSizeBytes());
-                    if (isolateStub == null) {
-                        throw new RuntimeException(
-                                "Service implementation doesn't support setting maximum heap size");
-                    }
-                }
+                isolate = JavaScriptIsolate.create(this, settings);
             } catch (RemoteException e) {
+                // TODO(b/286055647): Handle sandbox dying during createIsolate more sensibly.
                 throw new RuntimeException(e);
             }
-            return createJsIsolateLocked(isolateStub, settings);
+            mActiveIsolateSet.add(isolate);
+            return isolate;
+        }
+    }
+
+    // In practice, this method should only be called whilst already holding mLock, but it is
+    // called via JavaScriptIsolate and this constraint cannot be cleanly expressed via GuardedBy.
+    IJsSandboxIsolate createIsolateOnService(@NonNull IsolateStartupParameters settings,
+            @Nullable IJsSandboxIsolateClient isolateInstanceCallback) throws RemoteException {
+        synchronized (mLock) {
+            Objects.requireNonNull(mJsSandboxService);
+            if (isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_CLIENT)) {
+                return mJsSandboxService.createIsolate2(settings.getMaxHeapSizeBytes(),
+                        isolateInstanceCallback);
+            } else if (isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE)) {
+                return mJsSandboxService.createIsolateWithMaxHeapSizeBytes(
+                        settings.getMaxHeapSizeBytes());
+            } else {
+                return mJsSandboxService.createIsolate();
+            }
         }
     }
 
@@ -459,19 +498,12 @@
         if (features.contains(IJsSandboxService.CONSOLE_MESSAGING)) {
             featureSet.add(JS_FEATURE_CONSOLE_MESSAGING);
         }
+        if (features.contains(IJsSandboxService.ISOLATE_CLIENT + ":DEV")) {
+            featureSet.add(JS_FEATURE_ISOLATE_CLIENT);
+        }
         return featureSet;
     }
 
-    @GuardedBy("mLock")
-    @NonNull
-    @SuppressWarnings("NullAway")
-    private JavaScriptIsolate createJsIsolateLocked(@NonNull IJsSandboxIsolate isolateStub,
-            @NonNull IsolateStartupParameters settings) {
-        JavaScriptIsolate isolate = new JavaScriptIsolate(isolateStub, this, settings);
-        mActiveIsolateSet.add(isolate);
-        return isolate;
-    }
-
     /**
      * Checks whether a given feature is supported by the JS Sandbox implementation.
      * <p>
@@ -512,28 +544,51 @@
             if (mJsSandboxService == null) {
                 return;
             }
-            // This is the closest thing to a .close() method for ExecutorServices. This doesn't
-            // force the threads or their Runnables to immediately terminate, but will ensure
-            // that once the
-            // worker threads finish their current runnable (if any) that the thread pool terminates
-            // them, preventing a leak of threads.
-            mThreadPoolTaskExecutor.shutdownNow();
-            notifyIsolatesAboutClosureLocked();
-            mConnection.mContext.unbindService(mConnection);
+            unbindService();
             // Currently we consider that we are ready for a new connection once we unbind. This
             // might not be true if the process is not immediately killed by ActivityManager once it
             // is unbound.
             sIsReadyToConnect.set(true);
             mJsSandboxService = null;
         }
+        notifyIsolatesAboutClosure();
+        // This is the closest thing to a .close() method for ExecutorServices. This doesn't
+        // force the threads or their Runnables to immediately terminate, but will ensure
+        // that once the worker threads finish their current runnable (if any) that the thread
+        // pool terminates them, preventing a leak of threads.
+        mThreadPoolTaskExecutor.shutdownNow();
     }
 
-    @GuardedBy("mLock")
-    private void notifyIsolatesAboutClosureLocked() {
-        for (JavaScriptIsolate ele : mActiveIsolateSet) {
-            ele.maybeSetSandboxDead();
+    // Unbind the service if it hasn't been unbound already.
+    private void unbindService() {
+        final ConnectionSetup connection = mConnection.getAndSet(null);
+        if (connection != null) {
+            mContext.unbindService(connection);
         }
-        mActiveIsolateSet.clear();
+    }
+
+    // Kill the sandbox immediately.
+    //
+    // This will unbind the sandbox service so that any future IPC will fail immediately.
+    // However, isolates will be notified asynchronously, from the main thread.
+    void kill() {
+        unbindService();
+        // TODO(b/290750354, b/290749782): Implement separate closed vs dead state.
+        getMainExecutor().execute(this::close);
+    }
+
+    private void notifyIsolatesAboutClosure() {
+        // Do not hold mLock whilst calling into JavaScriptIsolate, as JavaScriptIsolate also has
+        // its own lock and may want to call into JavaScriptSandbox from another thread.
+        final Set<JavaScriptIsolate> activeIsolateSet;
+        synchronized (mLock) {
+            activeIsolateSet = mActiveIsolateSet;
+            mActiveIsolateSet = Collections.emptySet();
+        }
+        for (JavaScriptIsolate isolate : activeIsolateSet) {
+            isolate.maybeSetIsolateDead(new TerminationInfo(TerminationInfo.STATUS_SANDBOX_DEAD,
+                    "sandbox closed"));
+        }
     }
 
     @Override
@@ -553,6 +608,6 @@
 
     @NonNull
     Executor getMainExecutor() {
-        return ContextCompat.getMainExecutor(mConnection.mContext);
+        return ContextCompat.getMainExecutor(mContext);
     }
 }
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/MemoryLimitExceededException.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/MemoryLimitExceededException.java
index f4330cc..415b2d7 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/MemoryLimitExceededException.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/MemoryLimitExceededException.java
@@ -17,31 +17,34 @@
 package androidx.javascriptengine;
 
 import androidx.annotation.NonNull;
+import androidx.core.util.Consumer;
+
+import java.util.concurrent.Executor;
 
 /**
- * Indicates that a JavaScriptIsolate's evaluation failed due to exceeding its heap size limit.
+ * Indicates that a JavaScriptIsolate's evaluation failed due to the isolate exceeding its heap
+ * size limit.
  * <p>
  * This exception may be thrown when exceeding the heap size limit configured for the isolate via
  * {@link IsolateStartupParameters}, or the default limit. Beware that it will not be thrown if the
  * Android system as a whole has run out of memory before the JavaScript environment has reached
  * its configured heap limit.
  * <p>
- * The isolate may not continue to be used after this exception has been thrown, and other pending
- * evaluations for the isolate will fail. The isolate may continue to hold onto resources (even if
- * explicitly closed) until the sandbox has been shutdown. Therefore, it is recommended that the
- * sandbox be restarted at the earliest opportunity in order to reclaim these resources.
+ * If an evaluation fails with a MemoryLimitExceededException, it does not imply that that
+ * particular evaluation was in any way responsible for any excessive memory usage.
+ * MemoryLimitExceededException will be raised for all unresolved and future requested evaluations
+ * regardless of their culpability.
  * <p>
- * Other isolates within the same sandbox may continue to be used, created, and closed as normal.
- * <p>
- * Beware that not all JavaScript sandbox service implementations (particularly older ones)
- * handle memory exhaustion equally gracefully, and may instead crash the entire sandbox (see
- * {@link SandboxDeadException}).
+ * An isolate may run out of memory outside of an explicit evaluation (such as in a microtask), so
+ * you should generally not use this exception to detect out of memory issues - instead, use
+ * {@link JavaScriptIsolate#addOnTerminatedCallback(Executor, Consumer)} and check
+ * for an isolate termination status of {@link TerminationInfo#STATUS_MEMORY_LIMIT_EXCEEDED}.
  */
-public final class MemoryLimitExceededException extends JavaScriptException {
-    public MemoryLimitExceededException(@NonNull String error) {
-        super(error);
-    }
+public final class MemoryLimitExceededException extends IsolateTerminatedException {
     public MemoryLimitExceededException() {
         super();
     }
+    public MemoryLimitExceededException(@NonNull String error) {
+        super(error);
+    }
 }
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/SandboxDeadException.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/SandboxDeadException.java
index 229dee08..f3439b7 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/SandboxDeadException.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/SandboxDeadException.java
@@ -16,19 +16,20 @@
 
 package androidx.javascriptengine;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
 /**
  * Exception thrown when evaluation is terminated due the {@link JavaScriptSandbox} being dead.
  * This can happen when {@link JavaScriptSandbox#close()} is called or when the sandbox process
  * is killed by the framework.
- * <p>
- * This is different from {@link IsolateTerminatedException} which occurs when the
- * {@link JavaScriptIsolate} within a {@link JavaScriptSandbox} is terminated.
- * <p>
- * This exception will continue to be thrown for all future evaluation requests on unclosed
- * isolates.
  */
-public final class SandboxDeadException extends JavaScriptException {
+public final class SandboxDeadException extends IsolateTerminatedException {
     public SandboxDeadException() {
         super();
     }
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public SandboxDeadException(@NonNull String message) {
+        super(message);
+    }
 }
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/TerminationInfo.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/TerminationInfo.java
new file mode 100644
index 0000000..fc828de
--- /dev/null
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/TerminationInfo.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.javascriptengine;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolateClient;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Information about how and why an isolate has terminated.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class TerminationInfo {
+    /**
+     * Termination status code for an isolate.
+     */
+    @IntDef({STATUS_UNKNOWN_ERROR, STATUS_SANDBOX_DEAD, STATUS_MEMORY_LIMIT_EXCEEDED})
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Status {
+    }
+
+    /**
+     * The isolate (but not necessarily the sandbox) has crashed for an unknown reason.
+     */
+    public static final int STATUS_UNKNOWN_ERROR = IJsSandboxIsolateClient.TERMINATE_UNKNOWN_ERROR;
+    /**
+     * The whole sandbox died (or was closed), taking this isolate with it.
+     */
+    public static final int STATUS_SANDBOX_DEAD = IJsSandboxIsolateClient.TERMINATE_SANDBOX_DEAD;
+    /**
+     * The isolate exceeded its heap size limit.
+     * <p>
+     * The isolate may continue to hold onto resources (even if explicitly closed) until the
+     * sandbox has been shutdown. If necessary, restart the sandbox at the earliest opportunity in
+     * order to reclaim these resources.
+     * <p>
+     * Note that memory exhaustion will kill the whole sandbox, so any other isolates within the
+     * same sandbox will be terminated with {@link #STATUS_SANDBOX_DEAD}.
+     */
+    public static final int STATUS_MEMORY_LIMIT_EXCEEDED =
+            IJsSandboxIsolateClient.TERMINATE_MEMORY_LIMIT_EXCEEDED;
+    @Status
+    private final int mStatus;
+    @NonNull
+    private final String mMessage;
+
+    TerminationInfo(@Status int status, @NonNull String message) {
+        mStatus = status;
+        mMessage = message;
+    }
+
+    /**
+     * Get the status code of the termination.
+     * <p>
+     * New status codes may be added with new JavaScriptEngine versions.
+     *
+     * @return status code of the termination.
+     */
+    @Status
+    public int getStatus() {
+        return mStatus;
+    }
+
+    /**
+     * Describe the status code of the termination.
+     * These strings are not stable between JavaScriptEngine versions.
+     *
+     * @return description of status code of the termination.
+     */
+    @NonNull
+    public String getStatusString() {
+        switch (mStatus) {
+            case STATUS_UNKNOWN_ERROR:
+                return "unknown error";
+            case STATUS_SANDBOX_DEAD:
+                return "sandbox dead";
+            case STATUS_MEMORY_LIMIT_EXCEEDED:
+                return "memory limit exceeded";
+            default:
+                return "unknown error code " + mStatus;
+        }
+    }
+
+    /**
+     * Get the message associated with this termination.
+     * The content or format of these messages is not stable between JavaScriptEngine versions.
+     *
+     * @return Human-readable message about the termination.
+     */
+    @NonNull
+    public String getMessage() {
+        return mMessage;
+    }
+
+    /**
+     * Describe the termination.
+     * The content or format of this description is not stable between JavaScriptEngine versions.
+     *
+     * @return Human-readable description of the termination.
+     */
+    @NonNull
+    @Override
+    public String toString() {
+        return getStatusString() + ": " + getMessage();
+    }
+
+    @NonNull
+    IsolateTerminatedException toJavaScriptException() {
+        switch (mStatus) {
+            case STATUS_SANDBOX_DEAD:
+                return new SandboxDeadException(this.toString());
+            case STATUS_MEMORY_LIMIT_EXCEEDED:
+                return new MemoryLimitExceededException(this.toString());
+            default:
+                return new IsolateTerminatedException(this.toString());
+        }
+    }
+}
diff --git a/javascriptengine/javascriptengine/src/main/stableAidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxIsolateClient.aidl b/javascriptengine/javascriptengine/src/main/stableAidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxIsolateClient.aidl
new file mode 100644
index 0000000..6574ae4
--- /dev/null
+++ b/javascriptengine/javascriptengine/src/main/stableAidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxIsolateClient.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.chromium.android_webview.js_sandbox.common;
+
+/**
+ * Callbacks for isolate events, not specific to evaluations.
+ * @hide
+ */
+interface IJsSandboxIsolateClient {
+    // These crash codes may be generated on either the client or service side.
+
+    // The isolate terminated for an unknown reason.
+    const int TERMINATE_UNKNOWN_ERROR = 1;
+    // The sandbox died.
+    //
+    // This is typically generated client-side as the service may die before it gets a chance to
+    // send a message to the client.
+    const int TERMINATE_SANDBOX_DEAD = 2;
+    // The isolate exceeded its heap size limit.
+    const int TERMINATE_MEMORY_LIMIT_EXCEEDED = 3;
+
+    /**
+     * Informs the client that the isolate should now be considered terminated.
+     *
+     * @param status  A status code describing the reason for the termination. Must be one of the
+     *                constants beginning "TERMINATE_".
+     * @param message Unstructured information about the termination. May be null.
+     */
+    void onTerminated(int status, String message) = 1;
+}
diff --git a/javascriptengine/javascriptengine/src/main/stableAidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl b/javascriptengine/javascriptengine/src/main/stableAidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl
index 0453988..8afe48c 100644
--- a/javascriptengine/javascriptengine/src/main/stableAidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl
+++ b/javascriptengine/javascriptengine/src/main/stableAidl/org/chromium/android_webview/js_sandbox/common/IJsSandboxService.aidl
@@ -16,6 +16,7 @@
 
 package org.chromium.android_webview.js_sandbox.common;
 import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate;
+import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolateClient;
 
 /**
  * Used by the embedding app to execute JavaScript in a sandboxed environment.
@@ -62,9 +63,21 @@
     const String CONSOLE_MESSAGING = "CONSOLE_MESSAGING";
 
     /**
+     * Feature flag indicating that the client may provide the service side with an
+     * IJsSandboxIsolateClient, allowing the service to call into the client regardless of ongoing
+     * evaluations.
+     */
+    const String ISOLATE_CLIENT = "ISOLATE_CLIENT";
+
+    /**
      * @return A list of feature names supported by this implementation.
      */
     List<String> getSupportedFeatures() = 1;
 
     IJsSandboxIsolate createIsolateWithMaxHeapSizeBytes(long maxHeapSize) = 2;
+
+    /**
+     * Create an isolate with a given heap size and service-to-client interface.
+     */
+    IJsSandboxIsolate createIsolate2(long maxHeapSize, IJsSandboxIsolateClient isolateClient) = 3;
 }
diff --git a/libraryversions.toml b/libraryversions.toml
index 0e3bb10..1f1cb09 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -21,7 +21,7 @@
 COLLECTION = "1.4.0-alpha01"
 COMPOSE = "1.6.0-alpha05"
 COMPOSE_COMPILER = "1.5.3"
-COMPOSE_MATERIAL3 = "1.2.0-alpha07"
+COMPOSE_MATERIAL3 = "1.2.0-alpha08"
 COMPOSE_MATERIAL3_ADAPTIVE = "1.0.0-alpha01"
 COMPOSE_RUNTIME_TRACING = "1.0.0-alpha05"
 CONSTRAINTLAYOUT = "2.2.0-alpha13"
@@ -44,6 +44,7 @@
 CORE_TELECOM = "1.0.0-alpha01"
 CORE_UWB = "1.0.0-alpha08"
 CREDENTIALS = "1.2.0-beta04"
+CREDENTIALS_FIDO_QUARANTINE = "1.0.0-alpha01"
 CURSORADAPTER = "1.1.0-alpha01"
 CUSTOMVIEW = "1.2.0-alpha03"
 CUSTOMVIEW_POOLINGCONTAINER = "1.1.0-alpha01"
@@ -110,7 +111,7 @@
 RECYCLERVIEW_SELECTION = "1.2.0-alpha02"
 REMOTECALLBACK = "1.0.0-alpha02"
 RESOURCEINSPECTION = "1.1.0-alpha01"
-ROOM = "2.6.0-rc01"
+ROOM = "2.7.0-alpha01"
 SAFEPARCEL = "1.0.0-alpha01"
 SAVEDSTATE = "1.3.0-alpha01"
 SECURITY = "1.1.0-alpha07"
@@ -124,7 +125,7 @@
 SLICE_BUILDERS_KTX = "1.0.0-alpha08"
 SLICE_REMOTECALLBACK = "1.0.0-alpha01"
 SLIDINGPANELAYOUT = "1.3.0-alpha01"
-SQLITE = "2.4.0-rc01"
+SQLITE = "2.5.0-alpha01"
 SQLITE_INSPECTOR = "2.1.0-alpha01"
 STABLE_AIDL = "1.0.0-alpha01"
 STARTUP = "1.2.0-alpha03"
@@ -201,6 +202,7 @@
 CORE_HAPTICS = { group = "androidx.core.haptics", atomicGroupVersion = "versions.CORE_HAPTICS" }
 CORE_UWB = { group = "androidx.core.uwb", atomicGroupVersion = "versions.CORE_UWB" }
 CREDENTIALS = { group = "androidx.credentials", atomicGroupVersion = "versions.CREDENTIALS" }
+CREDENTIALS_FIDO = { group = "androidx.credentials.credentials-fido", atomicGroupVersion = "versions.CREDENTIALS_FIDO_QUARANTINE" }
 CURSORADAPTER = { group = "androidx.cursoradapter", atomicGroupVersion = "versions.CURSORADAPTER" }
 CUSTOMVIEW = { group = "androidx.customview" }
 DATASTORE = { group = "androidx.datastore", atomicGroupVersion = "versions.DATASTORE" }
diff --git a/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.kt b/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.kt
index c8f3cc7..2d0872e 100644
--- a/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.kt
+++ b/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.kt
@@ -45,10 +45,14 @@
 @JvmName("map")
 @MainThread
 @CheckResult
+@Suppress("UNCHECKED_CAST")
 fun <X, Y> LiveData<X>.map(
     transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards Y)
 ): LiveData<Y> {
     val result = MediatorLiveData<Y>()
+    if (isInitialized) {
+        result.value = transform(value as X)
+    }
     result.addSource(this) { x -> result.value = transform(x) }
     return result
 }
@@ -113,18 +117,21 @@
 @JvmName("switchMap")
 @MainThread
 @CheckResult
+@Suppress("UNCHECKED_CAST")
 fun <X, Y> LiveData<X>.switchMap(
     transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards LiveData<Y>)?
 ): LiveData<Y> {
     val result = MediatorLiveData<Y>()
-    result.addSource(this, object : Observer<X> {
-        var liveData: LiveData<Y>? = null
-
-        override fun onChanged(value: X) {
-            val newLiveData = transform(value)
-            if (liveData === newLiveData) {
-                return
-            }
+    var liveData: LiveData<Y>? = null
+    if (isInitialized) {
+        liveData = transform(value as X)
+        if (liveData != null && liveData.isInitialized) {
+            result.value = liveData.value
+        }
+    }
+    result.addSource(this) { value: X ->
+        val newLiveData = transform(value)
+        if (liveData !== newLiveData) {
             if (liveData != null) {
                 result.removeSource(liveData!!)
             }
@@ -133,7 +140,7 @@
                 result.addSource(liveData!!) { y -> result.setValue(y) }
             }
         }
-    })
+    }
     return result
 }
 
diff --git a/lifecycle/lifecycle-livedata/src/test/java/androidx/lifecycle/TransformationsTest.kt b/lifecycle/lifecycle-livedata/src/test/java/androidx/lifecycle/TransformationsTest.kt
index 737abdb..e343219 100644
--- a/lifecycle/lifecycle-livedata/src/test/java/androidx/lifecycle/TransformationsTest.kt
+++ b/lifecycle/lifecycle-livedata/src/test/java/androidx/lifecycle/TransformationsTest.kt
@@ -64,6 +64,26 @@
     }
 
     @Test
+    fun testMap_initialValueIsSet() {
+        val initialValue = "value"
+        val source = MutableLiveData(initialValue)
+        val mapped = source.map { it }
+        assertThat(mapped.isInitialized, `is`(true))
+        assertThat(source.value, `is`(initialValue))
+        assertThat(mapped.value, `is`(initialValue))
+    }
+
+    @Test
+    fun testMap_initialValueNull() {
+        val source = MutableLiveData<String?>(null)
+        val output = "testOutput"
+        val mapped: LiveData<String?> = source.map { output }
+        assertThat(mapped.isInitialized, `is`(true))
+        assertThat(source.value, nullValue())
+        assertThat(mapped.value, `is`(output))
+    }
+
+    @Test
     fun testSwitchMap() {
         val trigger: LiveData<Int> = MutableLiveData()
         val first: LiveData<String> = MutableLiveData()
@@ -123,6 +143,36 @@
     }
 
     @Test
+    fun testSwitchMap_initialValueSet() {
+        val initialValue1 = "value1"
+        val original = MutableLiveData(true)
+        val source1 = MutableLiveData(initialValue1)
+
+        val switched = original.switchMap { source1 }
+        assertThat(switched.isInitialized, `is`(true))
+        assertThat(source1.value, `is`(initialValue1))
+        assertThat(switched.value, `is`(initialValue1))
+    }
+
+    @Test
+    fun testSwitchMap_noInitialValue_notInitialized() {
+        val original = MutableLiveData(true)
+        val source = MutableLiveData<String>()
+
+        val switched = original.switchMap { source }
+        assertThat(switched.isInitialized, `is`(false))
+    }
+
+    @Test
+    fun testSwitchMap_initialValueNull() {
+        val original = MutableLiveData<String?>(null)
+        val source = MutableLiveData<String?>()
+
+        val switched = original.switchMap { source }
+        assertThat(switched.isInitialized, `is`(false))
+    }
+
+    @Test
     fun testNoRedispatchSwitchMap() {
         val trigger: LiveData<Int> = MutableLiveData()
         val first: LiveData<String> = MutableLiveData()
diff --git a/lint-checks/src/main/java/androidx/build/lint/UnstableAidlAnnotationDetector.kt b/lint-checks/src/main/java/androidx/build/lint/UnstableAidlAnnotationDetector.kt
index 637bee0..8a9be96 100644
--- a/lint-checks/src/main/java/androidx/build/lint/UnstableAidlAnnotationDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/UnstableAidlAnnotationDetector.kt
@@ -40,6 +40,9 @@
  */
 private const val JAVA_PASSTHROUGH = "JavaPassthrough"
 
+private const val ANDROIDX_REQUIRESOPTIN = "androidx.annotation.RequiresOptIn"
+private const val KOTLIN_REQUIRESOPTIN = "kotlin.RequiresOptIn"
+
 class UnstableAidlAnnotationDetector : AidlDefinitionDetector() {
 
     override fun visitAidlParcelableDeclaration(context: Context, node: AidlParcelableDeclaration) {
@@ -84,8 +87,8 @@
                 )
                 // Determine if the class is annotated with RequiresOptIn.
                 psiClass?.annotations?.any { psiAnnotation ->
-                    // Either androidx.annotation or kotlin version is fine here.
-                    psiAnnotation.hasQualifiedName("RequiresOptIn")
+                    psiAnnotation.hasQualifiedName(ANDROIDX_REQUIRESOPTIN) ||
+                        psiAnnotation.hasQualifiedName(KOTLIN_REQUIRESOPTIN)
                 } ?: false
             } else {
                 false
diff --git a/lint-checks/src/test/java/androidx/build/lint/UnstableAidlAnnotationDetectorTest.kt b/lint-checks/src/test/java/androidx/build/lint/UnstableAidlAnnotationDetectorTest.kt
index 8789d3b..c8c9885 100644
--- a/lint-checks/src/test/java/androidx/build/lint/UnstableAidlAnnotationDetectorTest.kt
+++ b/lint-checks/src/test/java/androidx/build/lint/UnstableAidlAnnotationDetectorTest.kt
@@ -131,6 +131,8 @@
             java(
                 "src/androidx/core/UnstableAidlDefinition.java",
                 """
+                    import androidx.annotation.RequiresOptIn;
+
                     @RequiresOptIn
                     public @interface UnstableAidlDefinition {}
                 """.trimIndent()
@@ -168,7 +170,7 @@
             java(
                 "src/androidx/core/UnstableAidlDefinition.java",
                 """
-                    @RequiresOptIn
+                    @androidx.annotation.RequiresOptIn
                     public @interface UnstableAidlDefinition {}
                 """.trimIndent()
             ),
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/GlobalMediaRouter.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/GlobalMediaRouter.java
index cb66015..231c532 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/GlobalMediaRouter.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/GlobalMediaRouter.java
@@ -115,6 +115,7 @@
             };
 
     private boolean mTransferReceiverDeclared;
+    private boolean mUseMediaRouter2ForSystemRouting;
     private MediaRoute2Provider mMr2Provider;
     private SystemMediaRouteProvider mSystemProvider;
     private DisplayManagerCompat mDisplayManager;
@@ -142,6 +143,15 @@
         mTransferReceiverDeclared =
                 Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
                         && MediaTransferReceiver.isDeclared(mApplicationContext);
+        mUseMediaRouter2ForSystemRouting =
+                SystemRoutingUsingMediaRouter2Receiver.isDeclared(mApplicationContext);
+
+        if (DEBUG && mUseMediaRouter2ForSystemRouting) {
+            // This is only added to skip the presubmit check for UnusedVariable
+            // TODO: Remove it once mUseMediaRouter2ForSystemRouting is actually used
+            Log.d(TAG, "Using MediaRouter2 for system routing");
+        }
+
         mMr2Provider =
                 Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && mTransferReceiverDeclared
                         ? new MediaRoute2Provider(mApplicationContext, new Mr2ProviderCallback())
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemRoutingUsingMediaRouter2Receiver.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemRoutingUsingMediaRouter2Receiver.java
new file mode 100644
index 0000000..ecc6df6
--- /dev/null
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemRoutingUsingMediaRouter2Receiver.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.mediarouter.media;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import java.util.List;
+
+/**
+ * A {@link BroadcastReceiver} class for enabling apps to get SystemRoutes using
+ * {@link android.media.MediaRouter2}.
+ */
+@RestrictTo(LIBRARY)
+final class SystemRoutingUsingMediaRouter2Receiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(@NonNull Context context, @NonNull Intent intent) {
+        // Do nothing for now.
+    }
+
+    /**
+     * Checks whether the {@link SystemRoutingUsingMediaRouter2Receiver} is declared in the app's
+     * manifest.
+     */
+    @RestrictTo(LIBRARY)
+    public static boolean isDeclared(@NonNull Context applicationContext) {
+        Intent queryIntent = new Intent(applicationContext,
+                SystemRoutingUsingMediaRouter2Receiver.class);
+        queryIntent.setPackage(applicationContext.getPackageName());
+        PackageManager pm = applicationContext.getPackageManager();
+        List<ResolveInfo> resolveInfos = pm.queryBroadcastReceivers(queryIntent, 0);
+
+        return resolveInfos.size() > 0;
+    }
+}
diff --git a/mediarouter/mediarouter/src/main/res/values-ne/strings.xml b/mediarouter/mediarouter/src/main/res/values-ne/strings.xml
index 490503a..7cdafb4 100644
--- a/mediarouter/mediarouter/src/main/res/values-ne/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-ne/strings.xml
@@ -39,7 +39,7 @@
     <string name="mr_controller_no_info_available" msgid="855271725131981086">"कुनै पनि जानकारी उपलब्ध छैन"</string>
     <string name="mr_controller_casting_screen" msgid="9171231064758955152">"स्क्रिन Cast गरिँदै छ"</string>
     <string name="mr_dialog_default_group_name" msgid="4115858704575247342">"समूह"</string>
-    <string name="mr_dialog_groupable_header" msgid="4307018456678388936">"डिभाइस थप्नुहोस्"</string>
+    <string name="mr_dialog_groupable_header" msgid="4307018456678388936">"डिभाइस कनेक्ट गर्नुहोस्"</string>
     <string name="mr_dialog_transferable_header" msgid="6068257520605505468">"कुनै समूहमा प्ले गर्नुहोस्"</string>
     <string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"कुनै पनि जानकारी उपलब्ध छैन"</string>
     <string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"कुनै पनि डिभाइस उपलब्ध छैन"</string>
diff --git a/metrics/integration-tests/janktest/build.gradle b/metrics/integration-tests/janktest/build.gradle
index 476a3ae..3aa5881 100644
--- a/metrics/integration-tests/janktest/build.gradle
+++ b/metrics/integration-tests/janktest/build.gradle
@@ -25,6 +25,9 @@
         viewBinding true
     }
     namespace "androidx.metrics.performance.janktest"
+    defaultConfig {
+        multiDexEnabled true
+    }
 }
 
 dependencies {
diff --git a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/DialogFragmentNavigatorTest.kt b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/DialogFragmentNavigatorTest.kt
index b434bcf..dfc8e5d 100644
--- a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/DialogFragmentNavigatorTest.kt
+++ b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/DialogFragmentNavigatorTest.kt
@@ -303,6 +303,193 @@
             .doesNotContain(dialogFragments[1])
     }
 
+    @UiThreadTest
+    @Test
+    fun testPop_transitioningDialogStaysInTransition() {
+        val dialogFragments = mutableListOf<DialogFragment>()
+        fragmentManager.fragmentFactory = object : FragmentFactory() {
+            override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
+                return super.instantiate(classLoader, className).also { fragment ->
+                    if (fragment is DialogFragment) {
+                        dialogFragments += fragment
+                    }
+                }
+            }
+        }
+
+        val firstEntry = createBackStackEntry()
+        val secondEntry = createBackStackEntry(2)
+        val thirdEntry = createBackStackEntry(3)
+
+        dialogNavigator.navigate(listOf(firstEntry), null, null)
+        dialogNavigator.navigate(listOf(secondEntry), null, null)
+        dialogNavigator.navigate(listOf(thirdEntry), null, null)
+        assertThat(navigatorState.backStack.value).containsExactly(
+            firstEntry, secondEntry, thirdEntry
+        ).inOrder()
+
+        dialogNavigator.popBackStack(secondEntry, false)
+        // should contain all entries as they have not moved to RESUMED state yet
+        assertThat(navigatorState.transitionsInProgress.value).containsExactly(
+            firstEntry, secondEntry, thirdEntry
+        )
+        fragmentManager.executePendingTransactions()
+        assertThat(navigatorState.transitionsInProgress.value).isEmpty()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testPop_nonTransitioningDialogMarkedComplete() {
+        val dialogFragments = mutableListOf<DialogFragment>()
+        fragmentManager.fragmentFactory = object : FragmentFactory() {
+            override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
+                return super.instantiate(classLoader, className).also { fragment ->
+                    if (fragment is DialogFragment) {
+                        dialogFragments += fragment
+                    }
+                }
+            }
+        }
+
+        val firstEntry = createBackStackEntry()
+        val secondEntry = createBackStackEntry(2)
+        val thirdEntry = createBackStackEntry(3)
+
+        dialogNavigator.navigate(listOf(firstEntry), null, null)
+        dialogNavigator.navigate(listOf(secondEntry), null, null)
+        dialogNavigator.navigate(listOf(thirdEntry), null, null)
+        fragmentManager.executePendingTransactions()
+        assertThat(navigatorState.transitionsInProgress.value).isEmpty()
+
+        dialogNavigator.popBackStack(secondEntry, false)
+        // firstEntry was moved to RESUMED so it is no longer transitioning. It should not
+        // be in transition when dialog above it is getting popped.
+        assertThat(navigatorState.transitionsInProgress.value).doesNotContain(firstEntry)
+        fragmentManager.executePendingTransactions()
+        assertThat(navigatorState.transitionsInProgress.value).isEmpty()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testPush_transitioningDialogStaysInTransition() {
+        val dialogFragments = mutableListOf<DialogFragment>()
+        fragmentManager.fragmentFactory = object : FragmentFactory() {
+            override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
+                return super.instantiate(classLoader, className).also { fragment ->
+                    if (fragment is DialogFragment) {
+                        dialogFragments += fragment
+                    }
+                }
+            }
+        }
+
+        val entry = createBackStackEntry()
+        val secondEntry = createBackStackEntry(SECOND_FRAGMENT)
+
+        dialogNavigator.navigate(listOf(entry), null, null)
+        dialogNavigator.navigate(listOf(secondEntry), null, null)
+        // Both entries have not reached RESUME and should be in transition
+        assertThat(navigatorState.transitionsInProgress.value).containsExactly(
+            entry, secondEntry
+        ).inOrder()
+        fragmentManager.executePendingTransactions()
+        assertThat(navigatorState.transitionsInProgress.value).isEmpty()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testPush_nonTransitioningDialogMarkedComplete() {
+        val dialogFragments = mutableListOf<DialogFragment>()
+        fragmentManager.fragmentFactory = object : FragmentFactory() {
+            override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
+                return super.instantiate(classLoader, className).also { fragment ->
+                    if (fragment is DialogFragment) {
+                        dialogFragments += fragment
+                    }
+                }
+            }
+        }
+
+        val entry = createBackStackEntry()
+
+        dialogNavigator.navigate(listOf(entry), null, null)
+        fragmentManager.executePendingTransactions()
+        assertThat(dialogFragments[0].requireDialog().isShowing).isTrue()
+        assertThat(navigatorState.transitionsInProgress.value).isEmpty()
+
+        val secondEntry = createBackStackEntry(SECOND_FRAGMENT)
+
+        dialogNavigator.navigate(listOf(secondEntry), null, null)
+        fragmentManager.executePendingTransactions()
+        assertThat(dialogFragments[1].requireDialog().isShowing).isTrue()
+        // ensure outgoing entry (first entry) is not transitioning
+        assertThat(navigatorState.transitionsInProgress.value).isEmpty()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testConsecutiveNavigateLifecycle() {
+        val dialogFragments = mutableListOf<DialogFragment>()
+        fragmentManager.fragmentFactory = object : FragmentFactory() {
+            override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
+                return super.instantiate(classLoader, className).also { fragment ->
+                    if (fragment is DialogFragment) {
+                        dialogFragments += fragment
+                    }
+                }
+            }
+        }
+
+        val entry = createBackStackEntry()
+        dialogNavigator.navigate(listOf(entry), null, null)
+        fragmentManager.executePendingTransactions()
+
+        assertThat(entry.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+
+        val secondEntry = createBackStackEntry(SECOND_FRAGMENT)
+        dialogNavigator.navigate(listOf(secondEntry), null, null)
+        fragmentManager.executePendingTransactions()
+
+        assertThat(navigatorState.backStack.value).containsExactly(entry, secondEntry).inOrder()
+        assertThat(entry.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+        assertThat(secondEntry.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+    }
+
+    @UiThreadTest
+    @Test
+    fun testConsecutiveNavigateThenPopLifecycle() {
+        val dialogFragments = mutableListOf<DialogFragment>()
+        fragmentManager.fragmentFactory = object : FragmentFactory() {
+            override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
+                return super.instantiate(classLoader, className).also { fragment ->
+                    if (fragment is DialogFragment) {
+                        dialogFragments += fragment
+                    }
+                }
+            }
+        }
+
+        val entry = createBackStackEntry()
+        val secondEntry = createBackStackEntry(SECOND_FRAGMENT)
+
+        dialogNavigator.navigate(listOf(entry), null, null)
+        dialogNavigator.navigate(listOf(secondEntry), null, null)
+        fragmentManager.executePendingTransactions()
+
+        assertThat(navigatorState.backStack.value).containsExactly(entry, secondEntry).inOrder()
+        assertThat(entry.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+        assertThat(secondEntry.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+
+        // pop top dialog
+        dialogNavigator.popBackStack(secondEntry, false)
+        fragmentManager.executePendingTransactions()
+
+        assertThat(navigatorState.backStack.value).containsExactly(entry)
+        assertThat(entry.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+        assertThat(dialogFragments[0].lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+        assertThat(secondEntry.lifecycle.currentState).isEqualTo(Lifecycle.State.DESTROYED)
+    }
+
     private fun createBackStackEntry(
         destId: Int = INITIAL_FRAGMENT,
         clazz: KClass<out Fragment> = EmptyDialogFragment::class
diff --git a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
index dc60ee0..5ae8346 100644
--- a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
+++ b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
@@ -171,6 +171,7 @@
         assertWithMessage("Replacement Fragment should be the primary navigation Fragment")
             .that(fragmentManager.primaryNavigationFragment)
             .isSameInstanceAs(replacementFragment)
+        assertThat(navigatorState.transitionsInProgress.value).isEmpty()
     }
 
     @UiThreadTest
@@ -359,6 +360,7 @@
         assertWithMessage("Fragment should be the primary navigation Fragment after pop")
             .that(fragmentManager.primaryNavigationFragment)
             .isSameInstanceAs(fragment)
+        assertThat(navigatorState.transitionsInProgress.value).isEmpty()
     }
 
     @UiThreadTest
@@ -528,6 +530,26 @@
         assertWithMessage("PrimaryFragment should be the correct type")
             .that(fragmentManager.primaryNavigationFragment)
             .isNotInstanceOf(EmptyFragment::class.java)
+        assertThat(navigatorState.transitionsInProgress.value).isEmpty()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testMultipleNavigateFragmentTransactionsThenPopMultiple() {
+        val entry = createBackStackEntry()
+        val secondEntry = createBackStackEntry(SECOND_FRAGMENT, clazz = Fragment::class)
+        val thirdEntry = createBackStackEntry(THIRD_FRAGMENT)
+
+        // Push 3 fragments
+        fragmentNavigator.navigate(listOf(entry, secondEntry, thirdEntry), null, null)
+        fragmentManager.executePendingTransactions()
+
+        // Now pop multiple fragments with savedState so that the secondEntry does not get
+        // marked complete by clear viewModel
+        fragmentNavigator.popBackStack(secondEntry, true)
+        fragmentManager.executePendingTransactions()
+        assertThat(navigatorState.backStack.value).containsExactly(entry)
+        assertThat(navigatorState.transitionsInProgress.value).isEmpty()
     }
 
     @UiThreadTest
diff --git a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt
index 52c924c..b7e0009 100644
--- a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt
+++ b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt
@@ -77,6 +77,9 @@
         assertWithMessage("New Entry should be RESUMED")
             .that(navController.currentBackStackEntry!!.lifecycle.currentState)
             .isEqualTo(Lifecycle.State.RESUMED)
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry!!
+        )
     }
 
     @Test
@@ -217,6 +220,7 @@
         // ensure original Fragment is dismissed and backStacks are in sync
         assertThat(navigator.backStack.value.size).isEqualTo(1)
         assertThat(fm.fragments.size).isEqualTo(2) // start + dialog fragment
+        assertThat(navController.visibleEntries.value.size).isEqualTo(2)
     }
 
     @Test
@@ -310,6 +314,9 @@
         fm?.executePendingTransactions()
 
         assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo("first")
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
     }
 
     @LargeTest
@@ -335,6 +342,9 @@
         onBackPressedDispatcher.onBackPressed()
 
         assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo("third")
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry!!
+        )
     }
 
     @LargeTest
@@ -364,6 +374,9 @@
         onBackPressedDispatcher.onBackPressed()
 
         assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo("fourth")
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry!!
+        )
     }
 
     private fun withNavigationActivity(
diff --git a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.kt b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.kt
index 55defac..b7732f7 100644
--- a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.kt
+++ b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.kt
@@ -121,10 +121,15 @@
         }
         val beforePopList = state.backStack.value
         // Get the set of entries that are going to be popped
+        val popUpToIndex = beforePopList.indexOf(popUpTo)
         val poppedList = beforePopList.subList(
-            beforePopList.indexOf(popUpTo),
+            popUpToIndex,
             beforePopList.size
         )
+        // track transitioning state of incoming entry
+        val incomingEntry = beforePopList.elementAtOrNull(popUpToIndex - 1)
+        val incomingEntryTransitioning = state.transitionsInProgress.value.contains(incomingEntry)
+
         // Now go through the list in reversed order (i.e., starting from the most recently added)
         // and dismiss each dialog
         for (entry in poppedList.reversed()) {
@@ -134,6 +139,10 @@
             }
         }
         state.popWithTransition(popUpTo, savedState)
+        // if incoming entry was marked as transitioning by popWithTransition, mark it as complete
+        if (incomingEntry != null && !incomingEntryTransitioning) {
+            state.markTransitionComplete(incomingEntry)
+        }
     }
 
     public override fun createDestination(): Destination {
@@ -159,7 +168,13 @@
     ) {
         val dialogFragment = createDialogFragment(entry)
         dialogFragment.show(fragmentManager, entry.id)
+        val outGoingEntry = state.backStack.value.lastOrNull()
+        val outGoingEntryTransitioning = state.transitionsInProgress.value.contains(outGoingEntry)
         state.pushWithTransition(entry)
+        // if outgoing entry was put in Transition by push, mark complete here
+        if (outGoingEntry != null && !outGoingEntryTransitioning) {
+            state.markTransitionComplete(outGoingEntry)
+        }
     }
 
     override fun onLaunchSingleTop(backStackEntry: NavBackStackEntry) {
diff --git a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt
index 3fb3248..0c8b113 100644
--- a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt
+++ b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt
@@ -85,16 +85,14 @@
                 entry.id == fragment.tag
             }
             if (entry != null) {
-                if (!state.backStack.value.contains(entry)) {
-                    if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
-                        Log.v(
-                            TAG,
-                            "Marking transition complete for entry $entry " +
-                                "due to fragment $source lifecycle reaching DESTROYED"
-                        )
-                    }
-                    state.markTransitionComplete(entry)
+                if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+                    Log.v(
+                        TAG,
+                        "Marking transition complete for entry $entry " +
+                            "due to fragment $source lifecycle reaching DESTROYED"
+                    )
                 }
+                state.markTransitionComplete(entry)
             }
         }
     }
@@ -113,19 +111,16 @@
                 }
                 state.markTransitionComplete(entry)
             }
-            // Once the lifecycle reaches DESTROYED, if the entry is not in the back stack, we can
-            // mark the transition complete
+            // Once the lifecycle reaches DESTROYED, we can mark the transition complete
             if (event == Lifecycle.Event.ON_DESTROY) {
-                if (!state.backStack.value.contains(entry)) {
-                    if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
-                        Log.v(
-                            TAG,
-                            "Marking transition complete for entry $entry due " +
-                                "to fragment $owner view lifecycle reaching DESTROYED"
-                        )
-                    }
-                    state.markTransitionComplete(entry)
+                if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+                    Log.v(
+                        TAG,
+                        "Marking transition complete for entry $entry due " +
+                            "to fragment $owner view lifecycle reaching DESTROYED"
+                    )
                 }
+                state.markTransitionComplete(entry)
             }
         }
     }
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
index 3b662ee..2ddc413 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -186,6 +186,9 @@
         assertThat(navigator.backStack.size)
             .isEqualTo(1)
         assertThat(originalViewModel.isCleared).isTrue()
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
     }
 
     @UiThreadTest
@@ -215,6 +218,7 @@
         val newViewModel = ViewModelProvider(newBackStackEntry).get<TestAndroidViewModel>()
         assertThat(newBackStackEntry.id).isSameInstanceAs(originalBackStackEntry.id)
         assertThat(newViewModel).isSameInstanceAs(originalViewModel)
+        assertThat(navController.visibleEntries.value).containsExactly(newBackStackEntry)
     }
 
     @UiThreadTest
@@ -606,6 +610,9 @@
         navController.navigate(R.id.second_test)
         assertThat(navController.currentDestination?.id ?: 0).isEqualTo(R.id.second_test)
         assertThat(navigator.backStack.size).isEqualTo(2)
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
     }
 
     @UiThreadTest
@@ -2028,6 +2035,7 @@
             .isFalse()
         assertThat(navController.currentDestination).isNull()
         assertThat(navigator.backStack.size).isEqualTo(0)
+        assertThat(navController.visibleEntries.value).isEmpty()
     }
 
     @UiThreadTest
@@ -2074,6 +2082,9 @@
             .isTrue()
         assertThat(navController.currentDestination?.id ?: 0).isEqualTo(R.id.start_test)
         assertThat(navigator.backStack.size).isEqualTo(1)
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
     }
 
     @UiThreadTest
@@ -2092,6 +2103,9 @@
         navigator.popCurrent()
         assertThat(navController.currentDestination?.id ?: 0).isEqualTo(R.id.start_test)
         assertThat(navigator.backStack.size).isEqualTo(1)
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
     }
 
     @UiThreadTest
@@ -2132,6 +2146,9 @@
         )
         assertThat(navController.currentDestination?.id ?: 0).isEqualTo(R.id.second_test)
         assertThat(navigator.backStack.size).isEqualTo(1)
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
     }
 
     @UiThreadTest
@@ -2172,6 +2189,9 @@
             .isTrue()
         assertThat(navController.currentDestination?.id ?: 0).isEqualTo(R.id.start_test)
         assertThat(navigator.backStack.size).isEqualTo(1)
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
     }
 
     @UiThreadTest
@@ -2229,6 +2249,9 @@
         navController.navigate(R.id.self)
         assertThat(navController.currentDestination?.id ?: 0).isEqualTo(R.id.second_test)
         assertThat(navigator.backStack.size).isEqualTo(2)
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
     }
 
     @UiThreadTest
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
index 51d0426..87d7074 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
@@ -126,8 +126,9 @@
      * whenever they change. If there is no visible [NavBackStackEntry], this will be set to an
      * empty list.
      *
-     * - `CREATED` entries are listed first and include all entries that have been popped from
-     * the back stack and are in the process of completing their exit transition
+     * - `CREATED` entries are listed first and include all entries that are in the process of
+     * completing their exit transition. Note that this can include entries that have been
+     * popped off the Navigation back stack.
      * - `STARTED` entries on the back stack are next and include all entries that are running
      * their enter transition and entries whose destination is partially covered by a
      * `FloatingWindow` destination
diff --git a/privacysandbox/sdkruntime/sdkruntime-client/build.gradle b/privacysandbox/sdkruntime/sdkruntime-client/build.gradle
index 5de128a..e8985b4 100644
--- a/privacysandbox/sdkruntime/sdkruntime-client/build.gradle
+++ b/privacysandbox/sdkruntime/sdkruntime-client/build.gradle
@@ -92,7 +92,6 @@
 dependencies {
     api(libs.kotlinStdlib)
     api(libs.kotlinCoroutinesCore)
-    implementation(libs.multidex)
     implementation("androidx.core:core-ktx:1.12.0-alpha05")
 
     api project(path: ':privacysandbox:sdkruntime:sdkruntime-core')
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
index abc5297..e45d496 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/java/androidx/privacysandbox/ui/integration/testapp/MainActivity.kt
@@ -81,9 +81,10 @@
 
         mSandboxedSdkView2 = SandboxedSdkView(this@MainActivity)
         mSandboxedSdkView2.addStateChangedListener(StateChangeListener(mSandboxedSdkView2))
-        mSandboxedSdkView2.layoutParams = ViewGroup.LayoutParams(400, 400)
+        mSandboxedSdkView2.layoutParams = findViewById<LinearLayout>(
+            R.id.bottom_banner_container).layoutParams
         runOnUiThread {
-            findViewById<LinearLayout>(R.id.ad_layout).addView(mSandboxedSdkView2)
+            findViewById<LinearLayout>(R.id.bottom_banner_container).addView(mSandboxedSdkView2)
         }
         mSandboxedSdkView2.setAdapter(SandboxedUiAdapterFactory.createFromCoreLibInfo(
             sdkApi.loadAd(/*isWebView=*/ false, "Hey!")
diff --git a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/activity_main.xml b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/activity_main.xml
index dda74f2..1051e95 100644
--- a/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/activity_main.xml
+++ b/privacysandbox/ui/integration-tests/testapp/src/main/res/layout/activity_main.xml
@@ -15,17 +15,20 @@
   limitations under the License.
   -->
 
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:weightSum="5"
+    android:orientation="vertical"
     tools:context=".MainActivity">
 
     <ScrollView
         android:id="@+id/scroll_view"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
+        android:layout_weight="4"
+        android:layout_height="0dp"
         android:orientation="vertical">
         <LinearLayout
             android:id="@+id/ad_layout"
@@ -80,4 +83,10 @@
                 android:text="@string/long_text" />
         </LinearLayout>
     </ScrollView>
-</androidx.constraintlayout.widget.ConstraintLayout>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:id="@+id/bottom_banner_container"
+        android:orientation="vertical" />
+</androidx.appcompat.widget.LinearLayoutCompat>
diff --git a/privacysandbox/ui/ui-client/build.gradle b/privacysandbox/ui/ui-client/build.gradle
index 2ef9295..41e0a3c 100644
--- a/privacysandbox/ui/ui-client/build.gradle
+++ b/privacysandbox/ui/ui-client/build.gradle
@@ -46,6 +46,7 @@
     androidTestImplementation(libs.espressoCore)
     androidTestImplementation(libs.mockitoCore)
     androidTestImplementation(libs.multidex)
+    androidTestImplementation(libs.testUiautomator)
     androidTestImplementation project(path: ':appcompat:appcompat')
 }
 
diff --git a/privacysandbox/ui/ui-client/src/androidTest/AndroidManifest.xml b/privacysandbox/ui/ui-client/src/androidTest/AndroidManifest.xml
index b2720e1..15f02d9 100644
--- a/privacysandbox/ui/ui-client/src/androidTest/AndroidManifest.xml
+++ b/privacysandbox/ui/ui-client/src/androidTest/AndroidManifest.xml
@@ -14,7 +14,10 @@
   See the License for the specific language governing permissions and
   limitations under the License.
   -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+    <!-- This override is okay because the associated tests only run on T+ -->
+    <uses-sdk tools:overrideLibrary="android_libs.ub_uiautomator, androidx.test.uiautomator" />
     <application android:supportsRtl="true"
         android:name="androidx.multidex.MultiDexApplication"
         android:theme="@style/Theme.AppCompat">
diff --git a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
index fd13072..7498242 100644
--- a/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
+++ b/privacysandbox/ui/ui-client/src/androidTest/java/androidx/privacysandbox/ui/client/test/SandboxedSdkViewTest.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.content.pm.ActivityInfo
 import android.content.res.Configuration
+import android.graphics.Rect
 import android.os.Build
 import android.os.IBinder
 import android.view.SurfaceView
@@ -27,15 +28,22 @@
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
 import android.widget.LinearLayout
+import android.widget.ScrollView
 import androidx.annotation.RequiresApi
 import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionState
 import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionStateChangedListener
 import androidx.privacysandbox.ui.client.view.SandboxedSdkView
 import androidx.privacysandbox.ui.core.SandboxedUiAdapter
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withId
 import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
 import androidx.testutils.withActivity
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
@@ -45,7 +53,6 @@
 import org.junit.Assert.assertTrue
 import org.junit.Assume
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -58,6 +65,8 @@
 
     companion object {
         const val TIMEOUT = 1000.toLong()
+        // Longer timeout used for expensive operations like device rotation.
+        const val UI_INTENSIVE_TIMEOUT = 2000.toLong()
     }
 
     private lateinit var context: Context
@@ -183,7 +192,7 @@
         Assume.assumeTrue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
         context = InstrumentationRegistry.getInstrumentation().targetContext
         activity = activityScenarioRule.withActivity { this }
-        view = SandboxedSdkView(context)
+        view = SandboxedSdkView(activity)
         stateChangedListener = StateChangedListener()
         view.addStateChangedListener(stateChangedListener)
 
@@ -338,17 +347,19 @@
     }
 
     @Test
-    @Ignore("b/272324246")
     fun onConfigurationChangedTest() {
         addViewToLayout()
-
-        openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
-        assertTrue(openSessionLatch.count == 0.toLong())
-        activity.runOnUiThread {
-            activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
-        }
-        configChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
-        assertTrue(configChangedLatch.count == 0.toLong())
+        val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+        assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        // newWindow() will be triggered by a window state change, even if the activity handles
+        // orientation changes without recreating the activity.
+        device.performActionAndWait({
+            device.setOrientationLeft()
+        }, Until.newWindow(), UI_INTENSIVE_TIMEOUT)
+        assertThat(configChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        device.performActionAndWait({
+            device.setOrientationNatural()
+        }, Until.newWindow(), UI_INTENSIVE_TIMEOUT)
     }
 
     @Test
@@ -358,7 +369,7 @@
         activity.runOnUiThread {
             activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
         }
-        assertThat(configChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
+        assertThat(configChangedLatch.await(UI_INTENSIVE_TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
     }
 
     @Test
@@ -462,6 +473,33 @@
         assertThat(testSandboxedUiAdapter.inputToken).isEqualTo(token)
     }
 
+    @Test
+    fun getBoundingParent_withoutScrollParent() {
+        addViewToLayout()
+        onView(withId(R.id.mainlayout)).check(matches(isDisplayed()))
+        val boundingRect = Rect()
+        assertThat(view.getBoundingParent(boundingRect)).isTrue()
+        val rootView: ViewGroup = activity.findViewById(android.R.id.content)
+        val rootRect = Rect()
+        rootView.getGlobalVisibleRect(rootRect)
+        assertThat(boundingRect).isEqualTo(rootRect)
+    }
+
+    @Test
+    fun getBoundingParent_withScrollParent() {
+        val scrollViewRect = Rect()
+        val scrollView = activity.findViewById<ScrollView>(R.id.scroll_view)
+        activity.runOnUiThread {
+            scrollView.visibility = View.VISIBLE
+            scrollView.addView(view)
+        }
+        onView(withId(R.id.scroll_view)).check(matches(isDisplayed()))
+        assertThat(scrollView.getGlobalVisibleRect(scrollViewRect)).isTrue()
+        val boundingRect = Rect()
+        assertThat(view.getBoundingParent(boundingRect)).isTrue()
+        assertThat(scrollViewRect).isEqualTo(boundingRect)
+    }
+
     /**
      * Ensures that ACTIVE will only be sent to registered state change listeners after the next
      * frame commit.
diff --git a/privacysandbox/ui/ui-client/src/androidTest/res/layout/activity_main.xml b/privacysandbox/ui/ui-client/src/androidTest/res/layout/activity_main.xml
index 9922ac7..71da52f 100644
--- a/privacysandbox/ui/ui-client/src/androidTest/res/layout/activity_main.xml
+++ b/privacysandbox/ui/ui-client/src/androidTest/res/layout/activity_main.xml
@@ -19,4 +19,9 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:id="@+id/mainlayout">
+    <ScrollView
+        android:layout_width="500px"
+        android:layout_height="500px"
+        android:id="@+id/scroll_view"
+        android:visibility="gone" />
 </LinearLayout>
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
index de5b048..70ceae1 100644
--- a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
@@ -133,6 +133,7 @@
 
             override fun notifyZOrderChanged(isZOrderOnTop: Boolean) {
                 surfaceView.setZOrderOnTop(isZOrderOnTop)
+                remoteSessionController.notifyZOrderChanged(isZOrderOnTop)
             }
 
             override fun close() {
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
index bbf3694..34e205b 100644
--- a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
@@ -18,13 +18,22 @@
 
 import android.content.Context
 import android.content.res.Configuration
+import android.graphics.Rect
 import android.os.Build
 import android.os.IBinder
 import android.util.AttributeSet
+import android.view.SurfaceControl
+import android.view.SurfaceHolder
 import android.view.SurfaceView
 import android.view.View
 import android.view.ViewGroup
+import android.view.ViewParent
+import android.view.ViewTreeObserver
 import androidx.annotation.RequiresApi
+import androidx.annotation.VisibleForTesting
+import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionState.Active
+import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionState.Idle
+import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionState.Loading
 import androidx.privacysandbox.ui.core.SandboxedUiAdapter
 import java.util.concurrent.CopyOnWriteArrayList
 import kotlin.math.min
@@ -95,6 +104,24 @@
         visibility = GONE
     }
 
+    // This will only be invoked when the content view has been set and the window is attached.
+    private val surfaceChangedCallback = object : SurfaceHolder.Callback {
+        override fun surfaceCreated(p0: SurfaceHolder) {
+            setClippingBounds(true)
+            viewTreeObserver.addOnGlobalLayoutListener(globalLayoutChangeListener)
+        }
+
+        override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
+        }
+
+        override fun surfaceDestroyed(p0: SurfaceHolder) {
+        }
+    }
+
+    // This will only be invoked when the content view has been set and the window is attached.
+    private val globalLayoutChangeListener =
+        ViewTreeObserver.OnGlobalLayoutListener { setClippingBounds() }
+
     private var adapter: SandboxedUiAdapter? = null
     private var client: Client? = null
     private var isZOrderOnTop = true
@@ -103,6 +130,8 @@
     private var requestedHeight = -1
     private var isTransitionGroupSet = false
     private var windowInputToken: IBinder? = null
+    private var currentClippingBounds = Rect()
+    private var currentConfig = context.resources.configuration
     internal val stateListenerManager: StateListenerManager = StateListenerManager()
 
     /**
@@ -144,6 +173,60 @@
         checkClientOpenSession()
     }
 
+    internal fun setClippingBounds(forceUpdate: Boolean = false) {
+        checkNotNull(contentView)
+        check(isAttachedToWindow)
+
+        val updateRequired = getBoundingParent(currentClippingBounds) || forceUpdate
+        if (!updateRequired) {
+            return
+        }
+
+        val sv: SurfaceView = contentView as SurfaceView
+        val attachedSurfaceControl = checkNotNull(sv.rootSurfaceControl) {
+            "attachedSurfaceControl should be non-null if the window is attached"
+        }
+        val name = "clippingBounds-${System.currentTimeMillis()}"
+        val clippingBoundsSurfaceControl =
+            SurfaceControl.Builder().setName(name)
+                .build()
+        val reparentSurfaceControlTransaction = SurfaceControl.Transaction()
+            .reparent(sv.surfaceControl, clippingBoundsSurfaceControl)
+
+        val reparentClippingBoundsTransaction =
+            checkNotNull(
+                attachedSurfaceControl.buildReparentTransaction(clippingBoundsSurfaceControl)) {
+                "Reparent transaction should be non-null if the window is attached"
+            }
+        reparentClippingBoundsTransaction.setCrop(
+            clippingBoundsSurfaceControl, currentClippingBounds)
+        reparentClippingBoundsTransaction.setVisibility(
+            clippingBoundsSurfaceControl, true)
+        reparentSurfaceControlTransaction.merge(reparentClippingBoundsTransaction)
+        attachedSurfaceControl.applyTransactionOnDraw(reparentSurfaceControlTransaction)
+    }
+
+    /**
+     * Computes the window space coordinates for the bounding parent of this view, and stores the
+     * result in [rect].
+     *
+     * Returns true if the coordinates have changed, false otherwise.
+     */
+    @VisibleForTesting
+    internal fun getBoundingParent(rect: Rect): Boolean {
+        val prevBounds = Rect(rect)
+        var viewParent: ViewParent? = parent
+        while (viewParent != null && viewParent is View) {
+            val v = viewParent as View
+            if (v.isScrollContainer || v.id == android.R.id.content) {
+                v.getGlobalVisibleRect(rect)
+                return prevBounds != rect
+            }
+            viewParent = viewParent.getParent()
+        }
+        return false
+    }
+
     private fun checkClientOpenSession() {
         val adapter = adapter
         if (client == null && adapter != null && windowInputToken != null &&
@@ -197,11 +280,17 @@
     }
 
     private fun removeContentView() {
+        removeCallbacks()
         if (childCount == 1) {
             super.removeViewAt(0)
         }
     }
 
+    private fun removeCallbacks() {
+        (contentView as? SurfaceView)?.holder?.removeCallback(surfaceChangedCallback)
+        viewTreeObserver.removeOnGlobalLayoutListener(globalLayoutChangeListener)
+    }
+
     internal fun setContentView(contentView: View) {
         if (childCount > 1) {
             throw IllegalStateException("Number of children views must not exceed 1")
@@ -220,6 +309,10 @@
             stateListenerManager.currentUiSessionState =
                 SandboxedSdkUiSessionState.Active
         }
+
+        if (contentView is SurfaceView) {
+            contentView.holder.addCallback(surfaceChangedCallback)
+        }
     }
 
     internal fun onClientClosedSession(error: Throwable? = null) {
@@ -293,6 +386,7 @@
         client?.close()
         client = null
         windowInputToken = null
+        removeCallbacks()
         super.onDetachedFromWindow()
     }
 
@@ -309,9 +403,11 @@
 
     override fun onConfigurationChanged(config: Configuration?) {
         requireNotNull(config) { "Config cannot be null" }
-        if (context.resources.configuration == config)
+        if (config == currentConfig) {
             return
+        }
         super.onConfigurationChanged(config)
+        currentConfig = config
         client?.notifyConfigurationChanged(config)
         checkClientOpenSession()
     }
diff --git a/privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/IRemoteSessionController.aidl b/privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/IRemoteSessionController.aidl
index 1e2ff92..c856d33 100644
--- a/privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/IRemoteSessionController.aidl
+++ b/privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/IRemoteSessionController.aidl
@@ -21,4 +21,5 @@
     void close();
     void notifyConfigurationChanged(in Configuration configuration);
     void notifyResized(int width, int height);
+    void notifyZOrderChanged(boolean isZOrderOnTop);
 }
\ No newline at end of file
diff --git a/privacysandbox/ui/ui-provider/src/androidTest/kotlin/androidx/privacysandbox/ui/provider/test/BinderAdapterDelegateTest.kt b/privacysandbox/ui/ui-provider/src/androidTest/kotlin/androidx/privacysandbox/ui/provider/test/BinderAdapterDelegateTest.kt
index f2bb448..5b8aaab 100644
--- a/privacysandbox/ui/ui-provider/src/androidTest/kotlin/androidx/privacysandbox/ui/provider/test/BinderAdapterDelegateTest.kt
+++ b/privacysandbox/ui/ui-provider/src/androidTest/kotlin/androidx/privacysandbox/ui/provider/test/BinderAdapterDelegateTest.kt
@@ -93,15 +93,15 @@
     }
 
     @Test
-    fun touchFocusTransferredForSwipeLeft() {
+    fun touchFocusNotTransferredForSwipeLeft() {
         onView(withId(R.id.surface_view)).perform(swipeLeft())
-        assertThat(transferTouchFocusLatch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+        assertThat(transferTouchFocusLatch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isFalse()
     }
 
     @Test
-    fun touchFocusTransferredForSlowSwipeLeft() {
+    fun touchFocusNotTransferredForSlowSwipeLeft() {
         onView(withId(R.id.surface_view)).perform(slowSwipeLeft())
-        assertThat(transferTouchFocusLatch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+        assertThat(transferTouchFocusLatch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isFalse()
     }
 
     @Test
diff --git a/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderAdapterDelegate.kt b/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderAdapterDelegate.kt
index 1acb8cc..66e1da8 100644
--- a/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderAdapterDelegate.kt
+++ b/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderAdapterDelegate.kt
@@ -26,6 +26,7 @@
 import android.os.Handler
 import android.os.IBinder
 import android.os.Looper
+import android.util.Log
 import android.view.SurfaceControlViewHost
 import androidx.annotation.RequiresApi
 import androidx.annotation.VisibleForTesting
@@ -56,6 +57,11 @@
     private val adapter: SandboxedUiAdapter
 ) : ISandboxedUiAdapter.Stub() {
 
+    companion object {
+        private const val TAG = "BinderAdapterDelegate"
+        private const val FRAME_TIMEOUT_MILLIS = 1000.toLong()
+    }
+
     fun openSession(
         context: Context,
         windowInputToken: IBinder,
@@ -121,13 +127,25 @@
                 surfaceControlViewHost.setView(view, initialWidth, initialHeight)
             }
 
-            val surfacePackage = surfaceControlViewHost.surfacePackage
-            val remoteSessionController =
-                RemoteSessionController(surfaceControlViewHost, session)
-            remoteSessionClient.onRemoteSessionOpened(
-                surfacePackage, remoteSessionController,
-                /* isZOrderOnTop= */ true
-            )
+            // This var is not locked as it will be set to false by the first event that can trigger
+            // sending the remote session opened callback.
+            var alreadyOpenedSession = false
+            view.viewTreeObserver.registerFrameCommitCallback {
+                if (!alreadyOpenedSession) {
+                    alreadyOpenedSession = true
+                    sendRemoteSessionOpened(session)
+                }
+            }
+
+            // If a frame commit callback is not triggered within the timeout (such as when the
+            // screen is off), open the session anyway.
+            Handler(Looper.getMainLooper()).postDelayed({
+                if (!alreadyOpenedSession) {
+                    Log.w(TAG, "Frame not committed within $FRAME_TIMEOUT_MILLIS ms.")
+                    alreadyOpenedSession = true
+                    sendRemoteSessionOpened(session)
+                }
+            }, FRAME_TIMEOUT_MILLIS)
         }
 
         override fun onSessionError(throwable: Throwable) {
@@ -138,6 +156,16 @@
             remoteSessionClient.onResizeRequested(width, height)
         }
 
+        private fun sendRemoteSessionOpened(session: SandboxedUiAdapter.Session) {
+            val surfacePackage = surfaceControlViewHost.surfacePackage
+            val remoteSessionController =
+                RemoteSessionController(surfaceControlViewHost, session)
+            remoteSessionClient.onRemoteSessionOpened(
+                surfacePackage, remoteSessionController,
+                /* isZOrderOnTop= */ true
+            )
+        }
+
         @VisibleForTesting
         private inner class RemoteSessionController(
             val surfaceControlViewHost: SurfaceControlViewHost,
@@ -157,6 +185,10 @@
                 }
             }
 
+            override fun notifyZOrderChanged(isZOrderOnTop: Boolean) {
+                session.notifyZOrderChanged(isZOrderOnTop)
+            }
+
             override fun close() {
                 val mHandler = Handler(Looper.getMainLooper())
                 mHandler.post {
diff --git a/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/TouchFocusTransferringView.kt b/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/TouchFocusTransferringView.kt
index 1ef330a..132f6e3 100644
--- a/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/TouchFocusTransferringView.kt
+++ b/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/TouchFocusTransferringView.kt
@@ -25,11 +25,12 @@
 import android.widget.FrameLayout
 import androidx.annotation.RequiresApi
 import androidx.core.view.GestureDetectorCompat
+import kotlin.math.abs
 
 /**
  * A container [ViewGroup] that delegates touch events to the host or the UI provider.
  *
- * Touch events will first be passed to a scroll detector. If a scroll or fling
+ * Touch events will first be passed to a scroll detector. If a vertical scroll or fling
  * is detected, the gesture will be transferred to the host. Otherwise, the touch event will pass
  * through and be handled by the provider of UI.
  *
@@ -61,7 +62,7 @@
     /**
      * Handles intercepted touch events before they reach the UI provider.
      *
-     * If a scroll or fling event is caught, this is indicated by the [isScrolling] var.
+     * If a vertical scroll or fling event is caught, this is indicated by the [isScrolling] var.
      */
     private class ScrollDetector(context: Context) : GestureDetector.SimpleOnGestureListener() {
 
@@ -71,8 +72,12 @@
         private val gestureDetector: GestureDetectorCompat = GestureDetectorCompat(context, this)
 
         override fun onScroll(e1: MotionEvent?, e2: MotionEvent, dX: Float, dY: Float): Boolean {
-            isScrolling = true
-            return false
+            // A scroll is vertical if its y displacement is greater than its x displacement.
+            if (abs(dY) > abs(dX)) {
+                isScrolling = true
+                return false
+            }
+            return true
         }
 
         override fun onFling(
@@ -81,8 +86,12 @@
             velocityX: Float,
             velocityY: Float
         ): Boolean {
-            isScrolling = true
-            return false
+            // A fling is vertical if its y velocity is greater than its x velocity.
+            if (abs(velocityY) > abs(velocityX)) {
+                isScrolling = true
+                return false
+            }
+            return true
         }
 
         fun onTouchEvent(ev: MotionEvent) {
diff --git a/privacysandbox/ui/ui-tests/build.gradle b/privacysandbox/ui/ui-tests/build.gradle
index cf66642..77e6e30 100644
--- a/privacysandbox/ui/ui-tests/build.gradle
+++ b/privacysandbox/ui/ui-tests/build.gradle
@@ -44,6 +44,9 @@
 
 android {
     namespace "androidx.privacysandbox.ui.tests"
+    defaultConfig {
+        multiDexEnabled true
+    }
 }
 
 androidx {
diff --git a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
index ae63942..3333303 100644
--- a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
+++ b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
@@ -39,6 +39,7 @@
 import androidx.test.filters.MediumTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.testutils.withActivity
+import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
 import java.util.concurrent.TimeUnit
@@ -77,7 +78,7 @@
         errorLatch = CountDownLatch(1)
         stateChangeListener = TestStateChangeListener(errorLatch)
         view.addStateChangedListener(stateChangeListener)
-        activity.runOnUiThread(Runnable {
+        activity.runOnUiThread {
             val linearLayout = LinearLayout(context)
             linearLayout.layoutParams = LinearLayout.LayoutParams(
                 LinearLayout.LayoutParams.MATCH_PARENT,
@@ -86,7 +87,7 @@
             activity.setContentView(linearLayout)
             view.layoutParams = LinearLayout.LayoutParams(100, 100)
             linearLayout.addView(view)
-        })
+        }
     }
 
     @Ignore // b/271299184
@@ -166,7 +167,7 @@
         val adapter = TestSandboxedUiAdapter(openSessionLatch, null, false)
         val coreLibInfo = adapter.toCoreLibInfo(context)
         val adapterFromCoreLibInfo = SandboxedUiAdapterFactory.createFromCoreLibInfo(coreLibInfo)
-        var testSessionClient = TestSandboxedUiAdapter.TestSessionClient()
+        val testSessionClient = TestSandboxedUiAdapter.TestSessionClient()
 
         adapterFromCoreLibInfo.openSession(
             context,
@@ -178,10 +179,9 @@
             testSessionClient
         )
 
-        openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)
-        assertTrue(openSessionLatch.count == 0.toLong())
-        assertTrue(adapter.isOpenSessionCalled)
-        assertTrue(testSessionClient.isSessionOpened)
+        assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        assertThat(adapter.isOpenSessionCalled).isTrue()
+        assertThat(testSessionClient.isSessionOpened).isTrue()
     }
 
     @Test
@@ -203,6 +203,44 @@
         assertTrue(configChangedLatch.count == 0.toLong())
     }
 
+    /**
+     * Tests that the provider receives Z-order change updates.
+     */
+    @Test
+    fun testZOrderChanged() {
+        val openSessionLatch = CountDownLatch(1)
+        val adapter = TestSandboxedUiAdapter(
+            openSessionLatch,
+            null,
+            /* hasFailingTestSession=*/false
+        )
+        val coreLibInfo = adapter.toCoreLibInfo(context)
+        val adapterFromCoreLibInfo = SandboxedUiAdapterFactory.createFromCoreLibInfo(coreLibInfo)
+        view.setAdapter(adapterFromCoreLibInfo)
+        assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        view.setZOrderOnTopAndEnableUserInteraction(!adapter.initialZOrderOnTop)
+        assertThat(adapter.zOrderLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+    }
+
+    /**
+     * Tests that the provider does not receive Z-order updates if the Z-order is unchanged.
+     */
+    @Test
+    fun testZOrderUnchanged() {
+        val openSessionLatch = CountDownLatch(1)
+        val adapter = TestSandboxedUiAdapter(
+            openSessionLatch,
+            null,
+            /* hasFailingTestSession=*/false
+        )
+        val coreLibInfo = adapter.toCoreLibInfo(context)
+        val adapterFromCoreLibInfo = SandboxedUiAdapterFactory.createFromCoreLibInfo(coreLibInfo)
+        view.setAdapter(adapterFromCoreLibInfo)
+        assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        view.setZOrderOnTopAndEnableUserInteraction(adapter.initialZOrderOnTop)
+        assertThat(adapter.zOrderLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
+    }
+
     @Test
     fun testSessionError() {
         val adapter = TestSandboxedUiAdapter(
@@ -240,6 +278,8 @@
     ) : SandboxedUiAdapter {
 
         var isOpenSessionCalled = false
+        var initialZOrderOnTop = false
+        var zOrderLatch = CountDownLatch(1)
         lateinit var session: SandboxedUiAdapter.Session
         lateinit var internalClient: SandboxedUiAdapter.SessionClient
 
@@ -254,6 +294,7 @@
         ) {
             internalClient = client
             isOpenSessionCalled = true
+            initialZOrderOnTop = isZOrderOnTop
             session = if (hasFailingTestSession) {
                 FailingTestSession(context)
             } else {
@@ -301,6 +342,7 @@
             }
 
             override fun notifyZOrderChanged(isZOrderOnTop: Boolean) {
+                zOrderLatch.countDown()
             }
 
             override fun notifyConfigurationChanged(configuration: Configuration) {
@@ -312,11 +354,12 @@
         }
 
         class TestSessionClient : SandboxedUiAdapter.SessionClient {
-
-            var isSessionOpened = false
+            private val latch = CountDownLatch(1)
+            val isSessionOpened: Boolean
+                get() = latch.await(TIMEOUT, TimeUnit.MILLISECONDS)
 
             override fun onSessionOpened(session: SandboxedUiAdapter.Session) {
-                isSessionOpened = true
+                latch.countDown()
             }
 
             override fun onSessionError(throwable: Throwable) {
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt
index 6f0f335..f212956 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt
@@ -18,9 +18,11 @@
 
 import androidx.room.compiler.processing.util.Source
 import java.io.File
+import java.util.regex.Pattern
 
 private val BY_ROUNDS_PATH_PATTERN =
-    "(byRounds${File.separator}[0-9]+${File.separator})?(.*)".toPattern()
+    ("(byRounds${Pattern.quote(File.separator)}[0-9]+" +
+        "${Pattern.quote(File.separator)})?(.*)").toPattern()
 
 /**
  * Represents sources that are positioned in the [root] folder.
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XElement.kt
index 9dee083..c2c933e 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XElement.kt
@@ -61,10 +61,10 @@
 
     /**
      * Returns the immediate enclosing element. This uses Element.getEnclosingElement() on the
-     * Java side, and KSNode.parent on the KSP side. For non-nested classes we return null as we
-     * don't model packages yet. For fields declared in primary constructors in Kotlin we return
+     * Java side, and KSNode.parent on the KSP side. For non-nested classes we return null.
+     * For fields declared in primary constructors in Kotlin we return
      * the enclosing type, not the constructor. For top-level properties or functions in Kotlin
-     * we return JavacTypeElement on the Java side and KspFileMemberContainer or
+     * we return JavacTypeElement on the Javac/KAPT side and KspFileMemberContainer or
      * KspSyntheticFileMemberContainer on the KSP side.
      */
     val enclosingElement: XElement?
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XPackageElement.kt
similarity index 66%
copy from camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
copy to room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XPackageElement.kt
index 7a355f8..dcf7701 100644
--- a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XPackageElement.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.camera.effects;
+package androidx.room.compiler.processing
 
-import androidx.annotation.RestrictTo;
-
-/**
- * Provides a portrait post-processing effect.
- *
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class Portrait {
-    // TODO: implement this
+interface XPackageElement : XElement, XAnnotated {
+    val qualifiedName: String
 }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
index d0d6a55..98883ca 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
@@ -31,6 +31,11 @@
     val packageName: String
 
     /**
+     * The package that contains this element.
+     */
+    val packageElement: XPackageElement
+
+    /**
      * The type represented by this [XTypeElement].
      */
     override val type: XType
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/compat/XConverters.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/compat/XConverters.kt
index 03b17bf..52180c4 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/compat/XConverters.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/compat/XConverters.kt
@@ -52,10 +52,12 @@
 import androidx.room.compiler.processing.ksp.KspExecutableParameterElement
 import androidx.room.compiler.processing.ksp.KspExecutableType
 import androidx.room.compiler.processing.ksp.KspFieldElement
+import androidx.room.compiler.processing.ksp.KspFileMemberContainer
 import androidx.room.compiler.processing.ksp.KspProcessingEnv
 import androidx.room.compiler.processing.ksp.KspType
 import androidx.room.compiler.processing.ksp.KspTypeElement
 import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticContinuationParameterElement
+import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticFileMemberContainer
 import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticPropertyMethodElement
 import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticReceiverParameterElement
 import com.google.devtools.ksp.processing.Resolver
@@ -273,6 +275,8 @@
         }
     }
 
+    // Todo(kuanyingchou): consider adding `env` to XElement as the when expression may break
+    //  when we add new XElement subclasses.
     @Deprecated("This will be removed in a future version of XProcessing.")
     @JvmStatic
     fun XElement.getProcessingEnv(): XProcessingEnv {
@@ -282,6 +286,10 @@
             is KspSyntheticContinuationParameterElement -> this.env
             is KspSyntheticPropertyMethodElement -> this.env
             is KspSyntheticReceiverParameterElement -> this.env
+            is KspSyntheticPropertyMethodElement.Setter.SyntheticExecutableParameterElement ->
+                this.env
+            is KspFileMemberContainer -> this.env
+            is KspSyntheticFileMemberContainer -> this.env
             else -> error("Unexpected element: $this")
         }
     }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacPackageElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacPackageElement.kt
new file mode 100644
index 0000000..4519d9a
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacPackageElement.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.javac
+
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XMemberContainer
+import androidx.room.compiler.processing.XPackageElement
+import androidx.room.compiler.processing.javac.kotlin.KmFlags
+import java.lang.UnsupportedOperationException
+import javax.lang.model.element.PackageElement
+
+internal class JavacPackageElement(
+    env: JavacProcessingEnv,
+    private val packageElement: PackageElement
+) : JavacElement(env, packageElement), XPackageElement {
+    override val qualifiedName: String by lazy {
+        packageElement.qualifiedName.toString()
+    }
+    override val kotlinMetadata: KmFlags?
+        get() = null
+    override val name: String by lazy {
+        packageElement.simpleName.toString()
+    }
+    override val fallbackLocationText: String
+        get() = qualifiedName
+    override val enclosingElement: XElement?
+        get() = null
+    override val closestMemberContainer: XMemberContainer
+        get() = throw UnsupportedOperationException("Packages don't have a closestMemberContainer" +
+            " as we don't consider packages a member container for now and it" +
+            " has no enclosingElement.")
+}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
index 89bfdb6..1f614c7 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
@@ -312,10 +312,7 @@
                 wrapExecutableElement(element)
             }
             is PackageElement -> {
-                error(
-                    "Cannot get elements with annotation $annotationName. Package " +
-                        "elements are not supported by XProcessing."
-                )
+                JavacPackageElement(this, element)
             }
             else -> error("Unsupported element $element with annotation $annotationName")
         }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
index 3e30e02..506fcb2 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
@@ -49,9 +49,14 @@
     override val name: String
         get() = element.simpleName.toString()
 
+    override val packageName: String by lazy {
+        packageElement.qualifiedName
+    }
+
     @Suppress("UnstableApiUsage")
-    override val packageName: String
-        get() = MoreElements.getPackage(element).qualifiedName.toString()
+    override val packageElement: JavacPackageElement by lazy {
+        JavacPackageElement(env, MoreElements.getPackage(element))
+    }
 
     override val kotlinMetadata by lazy {
         KmClassContainer.createFor(env, element)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSDeclarationExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSDeclarationExt.kt
index 27d1c91..a8f4ef6 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSDeclarationExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSDeclarationExt.kt
@@ -70,7 +70,7 @@
     }
     // When a top level function/property is compiled, its containing class does not exist in KSP,
     // neither the file. So instead, we synthesize one
-    return KspSyntheticFileMemberContainer(ownerJvmClassName)
+    return KspSyntheticFileMemberContainer(env, ownerJvmClassName)
 }
 
 private fun KSDeclaration.findEnclosingAncestorClassDeclaration(): KSClassDeclaration? {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
index 9cce0e0..5d6a56b 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
@@ -113,12 +113,14 @@
  * Root package comes as <root> instead of "" so we work around it here.
  */
 internal fun KSDeclaration.getNormalizedPackageName(): String {
-    return packageName.asString().let {
-        if (it == "<root>") {
-            ""
-        } else {
-            it
-        }
+    return packageName.asString().getNormalizedPackageName()
+}
+
+internal fun String.getNormalizedPackageName(): String {
+    return if (this == "<root>") {
+        ""
+    } else {
+        this
     }
 }
 
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFileMemberContainer.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFileMemberContainer.kt
index 58171da..7a61c7b 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFileMemberContainer.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFileMemberContainer.kt
@@ -32,7 +32,7 @@
  * [XMemberContainer] implementation for KSFiles.
  */
 internal class KspFileMemberContainer(
-    private val env: KspProcessingEnv,
+    internal val env: KspProcessingEnv,
     private val ksFile: KSFile
 ) : KspMemberContainer,
     XAnnotated by KspAnnotated.create(
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspPackageElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspPackageElement.kt
new file mode 100644
index 0000000..4b34bd7
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspPackageElement.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.compiler.processing.ksp
+
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XMemberContainer
+import androidx.room.compiler.processing.XPackageElement
+import com.google.devtools.ksp.KspExperimental
+import com.google.devtools.ksp.symbol.KSAnnotation
+import java.lang.UnsupportedOperationException
+
+// This is not a KspElement as we don't have a backing model in KSP.
+internal class KspPackageElement(
+    env: KspProcessingEnv,
+    private val packageName: String
+) : KspAnnotated(env), XPackageElement {
+
+    override val qualifiedName: String by lazy {
+        packageName.getNormalizedPackageName()
+    }
+
+    override val name: String by lazy {
+        qualifiedName.substringAfterLast(".")
+    }
+
+    override fun kindName(): String = "package"
+
+    override val fallbackLocationText: String
+        get() = qualifiedName
+
+    override val docComment: String? = null
+
+    override fun validate(): Boolean = true
+
+    override val enclosingElement: XElement? = null
+    override val closestMemberContainer: XMemberContainer
+        get() = throw UnsupportedOperationException(
+            "Packages don't have a closestMemberContainer as we don't consider packages " +
+                "a member container for now and it has no enclosingElement.")
+
+    @OptIn(KspExperimental::class)
+    override fun annotations(): Sequence<KSAnnotation> {
+        return env.resolver.getPackageAnnotations(qualifiedName)
+    }
+}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspRoundEnv.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspRoundEnv.kt
index 094650c..8386afc 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspRoundEnv.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspRoundEnv.kt
@@ -19,6 +19,7 @@
 import androidx.room.compiler.processing.XElement
 import androidx.room.compiler.processing.XRoundEnv
 import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticPropertyMethodElement
+import com.google.devtools.ksp.KspExperimental
 import com.google.devtools.ksp.symbol.ClassKind
 import com.google.devtools.ksp.symbol.KSClassDeclaration
 import com.google.devtools.ksp.symbol.KSFunctionDeclaration
@@ -40,6 +41,7 @@
         )
     }
 
+    @OptIn(KspExperimental::class)
     override fun getElementsAnnotatedWith(annotationQualifiedName: String): Set<XElement> {
         if (annotationQualifiedName == "*") {
             return emptySet()
@@ -51,6 +53,7 @@
                         is KSPropertyDeclaration -> {
                            add(KspFieldElement.create(env, symbol))
                         }
+
                         is KSClassDeclaration -> {
                             when (symbol.classKind) {
                                 ClassKind.ENUM_ENTRY ->
@@ -58,9 +61,11 @@
                                 else -> add(KspTypeElement.create(env, symbol))
                             }
                         }
+
                         is KSFunctionDeclaration -> {
                             add(KspExecutableElement.create(env, symbol))
                         }
+
                         is KSPropertyAccessor -> {
                             if (symbol.receiver.isStatic() &&
                                 symbol.receiver.parentDeclaration is KSClassDeclaration &&
@@ -89,20 +94,27 @@
                                 )
                             }
                         }
+
                         is KSValueParameter -> {
                             add(KspExecutableParameterElement.create(env, symbol))
                         }
+
                         else ->
                             error("Unsupported $symbol with annotation $annotationQualifiedName")
                     }
                 }
-            }
-            .filter {
-                // Due to the bug in https://github.com/google/ksp/issues/1198, KSP may incorrectly
-                // copy annotations from a constructor KSValueParameter to its KSPropertyDeclaration
-                // which we remove manually, so check here to make sure this is in sync with the
-                // actual annotations on the element.
-                it.getAllAnnotations().any { it.qualifiedName == annotationQualifiedName }
-            }.toSet()
+
+            env.resolver.getPackagesWithAnnotation(annotationQualifiedName)
+                .forEach { packageName ->
+                    add(KspPackageElement(env, packageName))
+                }
+        }
+        .filter {
+            // Due to the bug in https://github.com/google/ksp/issues/1198, KSP may incorrectly
+            // copy annotations from a constructor KSValueParameter to its KSPropertyDeclaration
+            // which we remove manually, so check here to make sure this is in sync with the
+            // actual annotations on the element.
+            it.getAllAnnotations().any { it.qualifiedName == annotationQualifiedName }
+        }.toSet()
     }
 }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
index 7333f7d..8c0aeb1 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
@@ -124,7 +124,11 @@
             // This matches javac's Types#directSupertypes().
             listOf(env.requireType(TypeName.OBJECT)) + superInterfaces
         } else {
-            check(superClasses.size == 1)
+            check(superClasses.size == 1) {
+                "Class ${this.typeName} should have only one super class. Found" +
+                    " ${superClasses.size}" +
+                    " (${superClasses.joinToString { it.typeName.toString() }})."
+            }
             superClasses + superInterfaces
         }
     }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
index 44bb15d..51f1463 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
@@ -26,6 +26,7 @@
 import androidx.room.compiler.processing.XMemberContainer
 import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XNullability
+import androidx.room.compiler.processing.XPackageElement
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.XTypeParameterElement
@@ -68,7 +69,11 @@
     }
 
     override val packageName: String by lazy {
-        declaration.getNormalizedPackageName()
+        packageElement.qualifiedName
+    }
+
+    override val packageElement: XPackageElement by lazy {
+        KspPackageElement(env, declaration.packageName.asString())
     }
 
     override val enclosingTypeElement: XTypeElement? by lazy {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainer.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainer.kt
index 2f47522..cb2432f 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainer.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainer.kt
@@ -23,6 +23,7 @@
 import androidx.room.compiler.processing.XEquality
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.ksp.KspMemberContainer
+import androidx.room.compiler.processing.ksp.KspProcessingEnv
 import androidx.room.compiler.processing.ksp.KspType
 import com.google.devtools.ksp.symbol.KSDeclaration
 import com.squareup.javapoet.ClassName
@@ -37,6 +38,7 @@
  * https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.1
  */
 internal class KspSyntheticFileMemberContainer(
+    internal val env: KspProcessingEnv,
     private val binaryName: String
 ) : KspMemberContainer, XEquality {
     override val equalityItems: Array<out Any?> by lazy {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
index 1247381..3f57122 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
@@ -255,8 +255,8 @@
             return "synthetic property getter"
         }
 
-        private class SyntheticExecutableParameterElement(
-            private val env: KspProcessingEnv,
+        internal class SyntheticExecutableParameterElement(
+            internal val env: KspProcessingEnv,
             override val enclosingElement: Setter
         ) : XExecutableParameterElement,
             XAnnotated by KspAnnotated.create(
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XRoundEnvTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XRoundEnvTest.kt
index 78a6f4d..6a33c86 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XRoundEnvTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XRoundEnvTest.kt
@@ -123,6 +123,60 @@
     }
 
     @Test
+    fun getAnnotatedPackageElements() {
+        val source = Source.java(
+            // Packages can be annotated in `package-info.java` files.
+            "foo.bar.foobar.package-info",
+            """
+            @OtherAnnotation(value = "xx")
+            package foo.bar.foobar;
+            import androidx.room.compiler.processing.testcode.OtherAnnotation;
+            """.trimIndent()
+        )
+
+        runProcessorTest(listOf(source)) { testInvocation ->
+            (testInvocation.roundEnv.getElementsAnnotatedWith(
+                OtherAnnotation::class
+            ).single() as XPackageElement).apply {
+                assertThat(name).isEqualTo("foobar")
+                assertThat(qualifiedName).isEqualTo("foo.bar.foobar")
+                assertThat(kindName()).isEqualTo("package")
+                assertThat(validate()).isTrue()
+            }.getAllAnnotations().single().apply {
+                assertThat(qualifiedName)
+                    .isEqualTo("androidx.room.compiler.processing.testcode.OtherAnnotation")
+            }.annotationValues.single().apply {
+                assertThat(name).isEqualTo("value")
+                assertThat(value).isEqualTo("xx")
+            }
+        }
+    }
+
+    @Test
+    fun defaultPackage() {
+        val javaSource = Source.java(
+            "FooBar",
+            """
+            class FooBar {}
+            """.trimIndent()
+        )
+        val kotlinSource = Source.kotlin(
+            "FooBarKt.kt",
+            """
+            class FooBarKt
+            """.trimIndent()
+        )
+        runProcessorTest(listOf(javaSource, kotlinSource)) { testInvocation ->
+            testInvocation.processingEnv.requireTypeElement("FooBar").apply {
+                assertThat(packageName).isEqualTo("")
+            }
+            testInvocation.processingEnv.requireTypeElement("FooBarKt").apply {
+                assertThat(packageName).isEqualTo("")
+            }
+        }
+    }
+
+    @Test
     fun misalignedAnnotationTargetFailsCompilation() {
         val source = Source.kotlin(
             "Baz.kt",
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
index 1808bb1..0bb58a2 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
@@ -17,6 +17,7 @@
 package androidx.room.compiler.processing.ksp
 
 import androidx.kruth.assertThat
+import androidx.kruth.assertThrows
 import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.asClassName
@@ -620,4 +621,31 @@
             assertParamType(asMember.parameterTypes.first())
         }
     }
+
+    @Test
+    fun oneSuperClass() {
+        val src = Source.java(
+            "foo.bar.Baz",
+            """
+            package foo.bar;
+            class A {}
+            interface B {}
+            class Baz extends A implements B, C {}
+            """.trimIndent()
+        )
+        runKspTest(
+            listOf(src)
+        ) { invocation ->
+            val typeElement = invocation.processingEnv.requireTypeElement("foo.bar.Baz")
+            val exception = assertThrows(IllegalStateException::class) {
+                typeElement.type.superTypes
+            }
+            exception.hasMessageThat().isEqualTo(
+                "Class foo.bar.Baz should have only one super class." +
+                    " Found 2 (foo.bar.A, error.NonExistentClass).")
+            invocation.assertCompilationResult {
+                compilationDidFail()
+            }
+        }
+    }
 }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainerTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainerTest.kt
index 134aed5..aeb12a4 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainerTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticFileMemberContainerTest.kt
@@ -19,6 +19,7 @@
 import androidx.kruth.assertThat
 import androidx.kruth.assertWithMessage
 import androidx.room.compiler.processing.ksp.KspFieldElement
+import androidx.room.compiler.processing.ksp.KspProcessingEnv
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.compileFiles
 import androidx.room.compiler.processing.util.getField
@@ -56,7 +57,8 @@
             val className = elements.map {
                 val owner = invocation.kspResolver.getOwnerJvmClassName(it as KSPropertyDeclaration)
                 assertWithMessage(it.toString()).that(owner).isNotNull()
-                KspSyntheticFileMemberContainer(owner!!).asClassName()
+                KspSyntheticFileMemberContainer(
+                    invocation.processingEnv as KspProcessingEnv, owner!!).asClassName()
             }.first()
             assertThat(className.packageName).isEmpty()
             assertThat(className.simpleNames).containsExactly("AppKt")
@@ -135,7 +137,8 @@
                     val field = target.getField("member") as KspFieldElement
                     val owner = invocation.kspResolver.getOwnerJvmClassName(field.declaration)
                     assertWithMessage(qName).that(owner).isNotNull()
-                    val synthetic = KspSyntheticFileMemberContainer(owner!!)
+                    val synthetic = KspSyntheticFileMemberContainer(
+                        invocation.processingEnv as KspProcessingEnv, owner!!)
                     assertWithMessage(qName).that(target.asClassName())
                         .isEqualTo(synthetic.asClassName())
                 }
diff --git a/samples/MediaRoutingDemo/src/main/AndroidManifest.xml b/samples/MediaRoutingDemo/src/main/AndroidManifest.xml
index 8afa6de..c671c5a 100644
--- a/samples/MediaRoutingDemo/src/main/AndroidManifest.xml
+++ b/samples/MediaRoutingDemo/src/main/AndroidManifest.xml
@@ -87,6 +87,9 @@
         <receiver android:name="androidx.mediarouter.media.MediaTransferReceiver"
             android:exported="true" />
 
+        <receiver android:name="androidx.mediarouter.media.SystemRoutingUsingMediaRouter2Receiver"
+            android:exported="true" />
+
         <service
             android:name=".services.SampleMediaRouteProviderService"
             android:exported="true"
diff --git a/settings.gradle b/settings.gradle
index 75096da..7459ae1 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -707,6 +707,7 @@
 includeProject(":core:uwb:uwb-rxjava3", [BuildType.MAIN])
 includeProject(":credentials:credentials", [BuildType.MAIN])
 includeProject(":credentials:credentials-samples", "credentials/credentials/samples", [BuildType.MAIN])
+includeProject(":credentials:credentials-fido:credentials-fido", [BuildType.MAIN])
 includeProject(":credentials:credentials-play-services-auth", [BuildType.MAIN])
 includeProject(":credentials:credentials-provider", [BuildType.MAIN])
 includeProject(":cursoradapter:cursoradapter", [BuildType.MAIN])
diff --git a/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics.java b/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics.java
index 7158a10..cc3df0a 100644
--- a/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics.java
+++ b/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics.java
@@ -59,6 +59,7 @@
 @RunWith(AndroidJUnit4.class)
 @LargeTest
 @SdkSuppress(minSdkVersion = 19)
+@SuppressWarnings("deprecation")
 public class SliceSerializeMetrics {
 
     private static final boolean WRITE_SAMPLE_FILE = false;
diff --git a/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java b/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java
index 05482e8..b892033 100644
--- a/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java
+++ b/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java
@@ -39,6 +39,7 @@
 @RunWith(Parameterized.class)
 @SmallTest
 @SdkSuppress(minSdkVersion = 19)
+@SuppressWarnings("deprecation")
 public class SliceViewMetrics {
 
     private final int mMode;
diff --git a/slice/slice-builders-ktx/api/current.txt b/slice/slice-builders-ktx/api/current.txt
index 05623ec..6d9933f 100644
--- a/slice/slice-builders-ktx/api/current.txt
+++ b/slice/slice-builders-ktx/api/current.txt
@@ -1,49 +1,49 @@
 // Signature format: 4.0
 package androidx.slice.builders {
 
-  public final class CellBuilderDsl extends androidx.slice.builders.GridRowBuilder.CellBuilder {
-    ctor public CellBuilderDsl();
+  @Deprecated public final class CellBuilderDsl extends androidx.slice.builders.GridRowBuilder.CellBuilder {
+    ctor @Deprecated public CellBuilderDsl();
   }
 
-  public final class GridRowBuilderDsl extends androidx.slice.builders.GridRowBuilder {
-    ctor public GridRowBuilderDsl();
+  @Deprecated public final class GridRowBuilderDsl extends androidx.slice.builders.GridRowBuilder {
+    ctor @Deprecated public GridRowBuilderDsl();
   }
 
   public final class GridRowBuilderKt {
-    method public static inline androidx.slice.builders.GridRowBuilder cell(androidx.slice.builders.GridRowBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.CellBuilderDsl,kotlin.Unit> buildCell);
-    method public static inline androidx.slice.builders.GridRowBuilder seeMoreCell(androidx.slice.builders.GridRowBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.CellBuilderDsl,kotlin.Unit> buildCell);
+    method @Deprecated public static inline androidx.slice.builders.GridRowBuilder cell(androidx.slice.builders.GridRowBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.CellBuilderDsl,kotlin.Unit> buildCell);
+    method @Deprecated public static inline androidx.slice.builders.GridRowBuilder seeMoreCell(androidx.slice.builders.GridRowBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.CellBuilderDsl,kotlin.Unit> buildCell);
   }
 
-  public final class HeaderBuilderDsl extends androidx.slice.builders.ListBuilder.HeaderBuilder {
-    ctor public HeaderBuilderDsl();
+  @Deprecated public final class HeaderBuilderDsl extends androidx.slice.builders.ListBuilder.HeaderBuilder {
+    ctor @Deprecated public HeaderBuilderDsl();
   }
 
-  public final class InputRangeBuilderDsl extends androidx.slice.builders.ListBuilder.InputRangeBuilder {
-    ctor public InputRangeBuilderDsl();
+  @Deprecated public final class InputRangeBuilderDsl extends androidx.slice.builders.ListBuilder.InputRangeBuilder {
+    ctor @Deprecated public InputRangeBuilderDsl();
   }
 
-  public final class ListBuilderDsl extends androidx.slice.builders.ListBuilder {
-    ctor public ListBuilderDsl(android.content.Context context, android.net.Uri uri, long ttl);
+  @Deprecated public final class ListBuilderDsl extends androidx.slice.builders.ListBuilder {
+    ctor @Deprecated public ListBuilderDsl(android.content.Context context, android.net.Uri uri, long ttl);
   }
 
   public final class ListBuilderKt {
-    method public static inline androidx.slice.builders.ListBuilder gridRow(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.GridRowBuilderDsl,kotlin.Unit> buildGrid);
-    method public static inline androidx.slice.builders.ListBuilder header(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.HeaderBuilderDsl,kotlin.Unit> buildHeader);
-    method public static inline androidx.slice.builders.ListBuilder inputRange(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.InputRangeBuilderDsl,kotlin.Unit> buildInputRange);
-    method public static inline androidx.slice.Slice list(android.content.Context context, android.net.Uri uri, long ttl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.ListBuilderDsl,kotlin.Unit> addRows);
-    method public static inline androidx.slice.builders.ListBuilder range(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RangeBuilderDsl,kotlin.Unit> buildRange);
-    method public static inline androidx.slice.builders.ListBuilder row(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RowBuilderDsl,kotlin.Unit> buildRow);
-    method public static inline androidx.slice.builders.ListBuilder seeMoreRow(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RowBuilderDsl,kotlin.Unit> buildRow);
-    method public static androidx.slice.builders.SliceAction tapSliceAction(android.app.PendingIntent pendingIntent, androidx.core.graphics.drawable.IconCompat icon, optional int imageMode, CharSequence title);
-    method public static androidx.slice.builders.SliceAction toggleSliceAction(android.app.PendingIntent pendingIntent, optional androidx.core.graphics.drawable.IconCompat? icon, CharSequence title, boolean isChecked);
+    method @Deprecated public static inline androidx.slice.builders.ListBuilder gridRow(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.GridRowBuilderDsl,kotlin.Unit> buildGrid);
+    method @Deprecated public static inline androidx.slice.builders.ListBuilder header(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.HeaderBuilderDsl,kotlin.Unit> buildHeader);
+    method @Deprecated public static inline androidx.slice.builders.ListBuilder inputRange(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.InputRangeBuilderDsl,kotlin.Unit> buildInputRange);
+    method @Deprecated public static inline androidx.slice.Slice list(android.content.Context context, android.net.Uri uri, long ttl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.ListBuilderDsl,kotlin.Unit> addRows);
+    method @Deprecated public static inline androidx.slice.builders.ListBuilder range(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RangeBuilderDsl,kotlin.Unit> buildRange);
+    method @Deprecated public static inline androidx.slice.builders.ListBuilder row(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RowBuilderDsl,kotlin.Unit> buildRow);
+    method @Deprecated public static inline androidx.slice.builders.ListBuilder seeMoreRow(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RowBuilderDsl,kotlin.Unit> buildRow);
+    method @Deprecated public static androidx.slice.builders.SliceAction tapSliceAction(android.app.PendingIntent pendingIntent, androidx.core.graphics.drawable.IconCompat icon, optional int imageMode, CharSequence title);
+    method @Deprecated public static androidx.slice.builders.SliceAction toggleSliceAction(android.app.PendingIntent pendingIntent, optional androidx.core.graphics.drawable.IconCompat? icon, CharSequence title, boolean isChecked);
   }
 
-  public final class RangeBuilderDsl extends androidx.slice.builders.ListBuilder.RangeBuilder {
-    ctor public RangeBuilderDsl();
+  @Deprecated public final class RangeBuilderDsl extends androidx.slice.builders.ListBuilder.RangeBuilder {
+    ctor @Deprecated public RangeBuilderDsl();
   }
 
-  public final class RowBuilderDsl extends androidx.slice.builders.ListBuilder.RowBuilder {
-    ctor public RowBuilderDsl();
+  @Deprecated public final class RowBuilderDsl extends androidx.slice.builders.ListBuilder.RowBuilder {
+    ctor @Deprecated public RowBuilderDsl();
   }
 
 }
diff --git a/slice/slice-builders-ktx/api/restricted_current.txt b/slice/slice-builders-ktx/api/restricted_current.txt
index 05623ec..6d9933f 100644
--- a/slice/slice-builders-ktx/api/restricted_current.txt
+++ b/slice/slice-builders-ktx/api/restricted_current.txt
@@ -1,49 +1,49 @@
 // Signature format: 4.0
 package androidx.slice.builders {
 
-  public final class CellBuilderDsl extends androidx.slice.builders.GridRowBuilder.CellBuilder {
-    ctor public CellBuilderDsl();
+  @Deprecated public final class CellBuilderDsl extends androidx.slice.builders.GridRowBuilder.CellBuilder {
+    ctor @Deprecated public CellBuilderDsl();
   }
 
-  public final class GridRowBuilderDsl extends androidx.slice.builders.GridRowBuilder {
-    ctor public GridRowBuilderDsl();
+  @Deprecated public final class GridRowBuilderDsl extends androidx.slice.builders.GridRowBuilder {
+    ctor @Deprecated public GridRowBuilderDsl();
   }
 
   public final class GridRowBuilderKt {
-    method public static inline androidx.slice.builders.GridRowBuilder cell(androidx.slice.builders.GridRowBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.CellBuilderDsl,kotlin.Unit> buildCell);
-    method public static inline androidx.slice.builders.GridRowBuilder seeMoreCell(androidx.slice.builders.GridRowBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.CellBuilderDsl,kotlin.Unit> buildCell);
+    method @Deprecated public static inline androidx.slice.builders.GridRowBuilder cell(androidx.slice.builders.GridRowBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.CellBuilderDsl,kotlin.Unit> buildCell);
+    method @Deprecated public static inline androidx.slice.builders.GridRowBuilder seeMoreCell(androidx.slice.builders.GridRowBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.CellBuilderDsl,kotlin.Unit> buildCell);
   }
 
-  public final class HeaderBuilderDsl extends androidx.slice.builders.ListBuilder.HeaderBuilder {
-    ctor public HeaderBuilderDsl();
+  @Deprecated public final class HeaderBuilderDsl extends androidx.slice.builders.ListBuilder.HeaderBuilder {
+    ctor @Deprecated public HeaderBuilderDsl();
   }
 
-  public final class InputRangeBuilderDsl extends androidx.slice.builders.ListBuilder.InputRangeBuilder {
-    ctor public InputRangeBuilderDsl();
+  @Deprecated public final class InputRangeBuilderDsl extends androidx.slice.builders.ListBuilder.InputRangeBuilder {
+    ctor @Deprecated public InputRangeBuilderDsl();
   }
 
-  public final class ListBuilderDsl extends androidx.slice.builders.ListBuilder {
-    ctor public ListBuilderDsl(android.content.Context context, android.net.Uri uri, long ttl);
+  @Deprecated public final class ListBuilderDsl extends androidx.slice.builders.ListBuilder {
+    ctor @Deprecated public ListBuilderDsl(android.content.Context context, android.net.Uri uri, long ttl);
   }
 
   public final class ListBuilderKt {
-    method public static inline androidx.slice.builders.ListBuilder gridRow(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.GridRowBuilderDsl,kotlin.Unit> buildGrid);
-    method public static inline androidx.slice.builders.ListBuilder header(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.HeaderBuilderDsl,kotlin.Unit> buildHeader);
-    method public static inline androidx.slice.builders.ListBuilder inputRange(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.InputRangeBuilderDsl,kotlin.Unit> buildInputRange);
-    method public static inline androidx.slice.Slice list(android.content.Context context, android.net.Uri uri, long ttl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.ListBuilderDsl,kotlin.Unit> addRows);
-    method public static inline androidx.slice.builders.ListBuilder range(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RangeBuilderDsl,kotlin.Unit> buildRange);
-    method public static inline androidx.slice.builders.ListBuilder row(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RowBuilderDsl,kotlin.Unit> buildRow);
-    method public static inline androidx.slice.builders.ListBuilder seeMoreRow(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RowBuilderDsl,kotlin.Unit> buildRow);
-    method public static androidx.slice.builders.SliceAction tapSliceAction(android.app.PendingIntent pendingIntent, androidx.core.graphics.drawable.IconCompat icon, optional int imageMode, CharSequence title);
-    method public static androidx.slice.builders.SliceAction toggleSliceAction(android.app.PendingIntent pendingIntent, optional androidx.core.graphics.drawable.IconCompat? icon, CharSequence title, boolean isChecked);
+    method @Deprecated public static inline androidx.slice.builders.ListBuilder gridRow(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.GridRowBuilderDsl,kotlin.Unit> buildGrid);
+    method @Deprecated public static inline androidx.slice.builders.ListBuilder header(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.HeaderBuilderDsl,kotlin.Unit> buildHeader);
+    method @Deprecated public static inline androidx.slice.builders.ListBuilder inputRange(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.InputRangeBuilderDsl,kotlin.Unit> buildInputRange);
+    method @Deprecated public static inline androidx.slice.Slice list(android.content.Context context, android.net.Uri uri, long ttl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.ListBuilderDsl,kotlin.Unit> addRows);
+    method @Deprecated public static inline androidx.slice.builders.ListBuilder range(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RangeBuilderDsl,kotlin.Unit> buildRange);
+    method @Deprecated public static inline androidx.slice.builders.ListBuilder row(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RowBuilderDsl,kotlin.Unit> buildRow);
+    method @Deprecated public static inline androidx.slice.builders.ListBuilder seeMoreRow(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RowBuilderDsl,kotlin.Unit> buildRow);
+    method @Deprecated public static androidx.slice.builders.SliceAction tapSliceAction(android.app.PendingIntent pendingIntent, androidx.core.graphics.drawable.IconCompat icon, optional int imageMode, CharSequence title);
+    method @Deprecated public static androidx.slice.builders.SliceAction toggleSliceAction(android.app.PendingIntent pendingIntent, optional androidx.core.graphics.drawable.IconCompat? icon, CharSequence title, boolean isChecked);
   }
 
-  public final class RangeBuilderDsl extends androidx.slice.builders.ListBuilder.RangeBuilder {
-    ctor public RangeBuilderDsl();
+  @Deprecated public final class RangeBuilderDsl extends androidx.slice.builders.ListBuilder.RangeBuilder {
+    ctor @Deprecated public RangeBuilderDsl();
   }
 
-  public final class RowBuilderDsl extends androidx.slice.builders.ListBuilder.RowBuilder {
-    ctor public RowBuilderDsl();
+  @Deprecated public final class RowBuilderDsl extends androidx.slice.builders.ListBuilder.RowBuilder {
+    ctor @Deprecated public RowBuilderDsl();
   }
 
 }
diff --git a/slice/slice-builders-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest.kt b/slice/slice-builders-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest.kt
index a05802a..6faebd4 100644
--- a/slice/slice-builders-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest.kt
+++ b/slice/slice-builders-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest.kt
@@ -33,6 +33,7 @@
 
 @SdkSuppress(minSdkVersion = 19)
 @MediumTest
+@Suppress("DEPRECATION")
 class SliceBuildersKtxTest {
     private val testUri = Uri.parse("content://com.example.android.sliceuri")
     private val context = ApplicationProvider.getApplicationContext() as android.content.Context
diff --git a/slice/slice-builders-ktx/src/main/java/androidx/slice/builders/GridRowBuilder.kt b/slice/slice-builders-ktx/src/main/java/androidx/slice/builders/GridRowBuilder.kt
index 504b13b..9188617 100644
--- a/slice/slice-builders-ktx/src/main/java/androidx/slice/builders/GridRowBuilder.kt
+++ b/slice/slice-builders-ktx/src/main/java/androidx/slice/builders/GridRowBuilder.kt
@@ -20,6 +20,14 @@
  * Two implicit receivers that are annotated with @SliceMarker are not accessible in the same scope,
  * ensuring a type-safe DSL.
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 @SliceMarker
 class GridRowBuilderDsl : GridRowBuilder()
 
@@ -28,17 +36,41 @@
  * Two implicit receivers that are annotated with @SliceMarker are not accessible in the same scope,
  * ensuring a type-safe DSL.
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 @SliceMarker
 class CellBuilderDsl : GridRowBuilder.CellBuilder()
 
 /**
  * @see GridRowBuilder.addCell
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 inline fun GridRowBuilderDsl.cell(buildCell: CellBuilderDsl.() -> Unit) =
     addCell(CellBuilderDsl().apply { buildCell() })
 
 /**
  * @see GridRowBuilder.setSeeMoreCell
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 inline fun GridRowBuilderDsl.seeMoreCell(buildCell: CellBuilderDsl.() -> Unit) =
     setSeeMoreCell(CellBuilderDsl().apply { buildCell() })
diff --git a/slice/slice-builders-ktx/src/main/java/androidx/slice/builders/ListBuilder.kt b/slice/slice-builders-ktx/src/main/java/androidx/slice/builders/ListBuilder.kt
index 101ed21..a782bc4 100644
--- a/slice/slice-builders-ktx/src/main/java/androidx/slice/builders/ListBuilder.kt
+++ b/slice/slice-builders-ktx/src/main/java/androidx/slice/builders/ListBuilder.kt
@@ -32,6 +32,14 @@
  * Two implicit receivers that are annotated with @SliceMarker are not accessible in the same scope,
  * ensuring a type-safe DSL.
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 @SliceMarker
 class ListBuilderDsl(context: Context, uri: Uri, ttl: Long) : ListBuilder(context, uri, ttl)
 
@@ -40,6 +48,14 @@
  * Two implicit receivers that are annotated with @SliceMarker are not accessible in the same scope,
  * ensuring a type-safe DSL.
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 @SliceMarker
 class RowBuilderDsl : ListBuilder.RowBuilder()
 
@@ -48,6 +64,14 @@
  * Two implicit receivers that are annotated with @SliceMarker are not accessible in the same scope,
  * ensuring a type-safe DSL.
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 @SliceMarker
 class InputRangeBuilderDsl : ListBuilder.InputRangeBuilder()
 
@@ -56,6 +80,14 @@
  * Two implicit receivers that are annotated with @SliceMarker are not accessible in the same scope,
  * ensuring a type-safe DSL.
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 @SliceMarker
 class RangeBuilderDsl : ListBuilder.RangeBuilder()
 
@@ -64,6 +96,14 @@
  * Two implicit receivers that are annotated with @SliceMarker are not accessible in the same scope,
  * ensuring a type-safe DSL.
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 @SliceMarker
 class HeaderBuilderDsl : ListBuilder.HeaderBuilder()
 
@@ -94,6 +134,14 @@
  * </pre>
  * @see ListBuilder.build
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 inline fun list(
     context: Context,
     uri: Uri,
@@ -104,42 +152,98 @@
 /**
  * @see ListBuilder.setHeader
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 inline fun ListBuilderDsl.header(buildHeader: HeaderBuilderDsl.() -> Unit) =
     setHeader(HeaderBuilderDsl().apply { buildHeader() })
 
 /**
  * @see ListBuilder.addGridRow
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 inline fun ListBuilderDsl.gridRow(buildGrid: GridRowBuilderDsl.() -> Unit) =
     addGridRow(GridRowBuilderDsl().apply { buildGrid() })
 
 /**
  * @see ListBuilder.addRow
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 inline fun ListBuilderDsl.row(buildRow: RowBuilderDsl.() -> Unit) =
     addRow(RowBuilderDsl().apply { buildRow() })
 
 /**
  * @see ListBuilder.setSeeMoreRow
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 inline fun ListBuilderDsl.seeMoreRow(buildRow: RowBuilderDsl.() -> Unit) =
     setSeeMoreRow(RowBuilderDsl().apply { buildRow() })
 
 /**
  * @see ListBuilder.addInputRange
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 inline fun ListBuilderDsl.inputRange(buildInputRange: InputRangeBuilderDsl.() -> Unit) =
     addInputRange(InputRangeBuilderDsl().apply { buildInputRange() })
 
 /**
  * @see ListBuilder.addRange
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 inline fun ListBuilderDsl.range(buildRange: RangeBuilderDsl.() -> Unit) =
     addRange(RangeBuilderDsl().apply { buildRange() })
 
 /**
  * Factory method to build a tappable [SliceAction].
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 fun tapSliceAction(
     pendingIntent: PendingIntent,
     icon: IconCompat,
@@ -150,6 +254,14 @@
 /**
  * Factory method to build a toggleable [SliceAction].
  */
+@Deprecated(
+    """
+        Slice framework has been deprecated, it will not receive any updates moving forward.
+        If you are looking for a framework that handles communication across apps, 
+        consider using AppSearchManager.
+    """,
+    ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
 fun toggleSliceAction(
     pendingIntent: PendingIntent,
     icon: IconCompat? = null,
diff --git a/slice/slice-builders/api/current.txt b/slice/slice-builders/api/current.txt
index 1351608..6801791 100644
--- a/slice/slice-builders/api/current.txt
+++ b/slice/slice-builders/api/current.txt
@@ -1,190 +1,190 @@
 // Signature format: 4.0
 package androidx.slice.builders {
 
-  @RequiresApi(19) public class GridRowBuilder {
-    ctor public GridRowBuilder();
-    method public androidx.slice.builders.GridRowBuilder addCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
-    method public androidx.slice.builders.GridRowBuilder setContentDescription(CharSequence);
-    method public androidx.slice.builders.GridRowBuilder setLayoutDirection(int);
-    method public androidx.slice.builders.GridRowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.GridRowBuilder setSeeMoreAction(android.app.PendingIntent);
-    method public androidx.slice.builders.GridRowBuilder setSeeMoreAction(androidx.remotecallback.RemoteCallback);
-    method public androidx.slice.builders.GridRowBuilder setSeeMoreCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
+  @Deprecated @RequiresApi(19) public class GridRowBuilder {
+    ctor @Deprecated public GridRowBuilder();
+    method @Deprecated public androidx.slice.builders.GridRowBuilder addCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder setLayoutDirection(int);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder setSeeMoreAction(android.app.PendingIntent);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder setSeeMoreAction(androidx.remotecallback.RemoteCallback);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder setSeeMoreCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
   }
 
-  public static class GridRowBuilder.CellBuilder {
-    ctor public GridRowBuilder.CellBuilder();
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(androidx.core.graphics.drawable.IconCompat, int);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(androidx.core.graphics.drawable.IconCompat?, int, boolean);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder addOverlayText(CharSequence);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder addOverlayText(CharSequence?, boolean);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder addText(CharSequence);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder addText(CharSequence?, boolean);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(CharSequence);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(CharSequence?, boolean);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder setContentDescription(CharSequence);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(android.app.PendingIntent);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(androidx.remotecallback.RemoteCallback);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder setSliceAction(androidx.slice.builders.SliceAction);
+  @Deprecated public static class GridRowBuilder.CellBuilder {
+    ctor @Deprecated public GridRowBuilder.CellBuilder();
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(androidx.core.graphics.drawable.IconCompat, int);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(androidx.core.graphics.drawable.IconCompat?, int, boolean);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addOverlayText(CharSequence);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addOverlayText(CharSequence?, boolean);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addText(CharSequence);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addText(CharSequence?, boolean);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(CharSequence);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(CharSequence?, boolean);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(android.app.PendingIntent);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(androidx.remotecallback.RemoteCallback);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder setSliceAction(androidx.slice.builders.SliceAction);
   }
 
-  @RequiresApi(19) public class ListBuilder extends androidx.slice.builders.TemplateSliceBuilder {
-    ctor @RequiresApi(26) public ListBuilder(android.content.Context, android.net.Uri, java.time.Duration?);
-    ctor public ListBuilder(android.content.Context, android.net.Uri, long);
-    method public androidx.slice.builders.ListBuilder addAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder addGridRow(androidx.slice.builders.GridRowBuilder);
-    method public androidx.slice.builders.ListBuilder addInputRange(androidx.slice.builders.ListBuilder.InputRangeBuilder);
-    method public androidx.slice.builders.ListBuilder addRange(androidx.slice.builders.ListBuilder.RangeBuilder);
-    method public androidx.slice.builders.ListBuilder addRating(androidx.slice.builders.ListBuilder.RatingBuilder);
-    method public androidx.slice.builders.ListBuilder addRow(androidx.slice.builders.ListBuilder.RowBuilder);
-    method public androidx.slice.builders.ListBuilder addSelection(androidx.slice.builders.SelectionBuilder);
-    method public androidx.slice.builders.ListBuilder setAccentColor(@ColorInt int);
-    method public androidx.slice.builders.ListBuilder setHeader(androidx.slice.builders.ListBuilder.HeaderBuilder);
-    method @RequiresApi(21) public androidx.slice.builders.ListBuilder setHostExtras(android.os.PersistableBundle);
-    method public androidx.slice.builders.ListBuilder setIsError(boolean);
-    method public androidx.slice.builders.ListBuilder setKeywords(java.util.Set<java.lang.String!>);
-    method public androidx.slice.builders.ListBuilder setLayoutDirection(int);
-    method public androidx.slice.builders.ListBuilder setSeeMoreAction(android.app.PendingIntent);
-    method public androidx.slice.builders.ListBuilder setSeeMoreAction(androidx.remotecallback.RemoteCallback);
-    method public androidx.slice.builders.ListBuilder setSeeMoreRow(androidx.slice.builders.ListBuilder.RowBuilder);
-    field public static final int ACTION_WITH_LABEL = 6; // 0x6
-    field public static final int ICON_IMAGE = 0; // 0x0
-    field public static final long INFINITY = -1L; // 0xffffffffffffffffL
-    field public static final int LARGE_IMAGE = 2; // 0x2
-    field public static final int RANGE_MODE_DETERMINATE = 0; // 0x0
-    field public static final int RANGE_MODE_INDETERMINATE = 1; // 0x1
-    field public static final int RANGE_MODE_STAR_RATING = 2; // 0x2
-    field public static final int RAW_IMAGE_LARGE = 4; // 0x4
-    field public static final int RAW_IMAGE_SMALL = 3; // 0x3
-    field public static final int SMALL_IMAGE = 1; // 0x1
-    field public static final int UNKNOWN_IMAGE = 5; // 0x5
+  @Deprecated @RequiresApi(19) public class ListBuilder extends androidx.slice.builders.TemplateSliceBuilder {
+    ctor @Deprecated @RequiresApi(26) public ListBuilder(android.content.Context, android.net.Uri, java.time.Duration?);
+    ctor @Deprecated public ListBuilder(android.content.Context, android.net.Uri, long);
+    method @Deprecated public androidx.slice.builders.ListBuilder addAction(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder addGridRow(androidx.slice.builders.GridRowBuilder);
+    method @Deprecated public androidx.slice.builders.ListBuilder addInputRange(androidx.slice.builders.ListBuilder.InputRangeBuilder);
+    method @Deprecated public androidx.slice.builders.ListBuilder addRange(androidx.slice.builders.ListBuilder.RangeBuilder);
+    method @Deprecated public androidx.slice.builders.ListBuilder addRating(androidx.slice.builders.ListBuilder.RatingBuilder);
+    method @Deprecated public androidx.slice.builders.ListBuilder addRow(androidx.slice.builders.ListBuilder.RowBuilder);
+    method @Deprecated public androidx.slice.builders.ListBuilder addSelection(androidx.slice.builders.SelectionBuilder);
+    method @Deprecated public androidx.slice.builders.ListBuilder setAccentColor(@ColorInt int);
+    method @Deprecated public androidx.slice.builders.ListBuilder setHeader(androidx.slice.builders.ListBuilder.HeaderBuilder);
+    method @Deprecated @RequiresApi(21) public androidx.slice.builders.ListBuilder setHostExtras(android.os.PersistableBundle);
+    method @Deprecated public androidx.slice.builders.ListBuilder setIsError(boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder setKeywords(java.util.Set<java.lang.String!>);
+    method @Deprecated public androidx.slice.builders.ListBuilder setLayoutDirection(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder setSeeMoreAction(android.app.PendingIntent);
+    method @Deprecated public androidx.slice.builders.ListBuilder setSeeMoreAction(androidx.remotecallback.RemoteCallback);
+    method @Deprecated public androidx.slice.builders.ListBuilder setSeeMoreRow(androidx.slice.builders.ListBuilder.RowBuilder);
+    field @Deprecated public static final int ACTION_WITH_LABEL = 6; // 0x6
+    field @Deprecated public static final int ICON_IMAGE = 0; // 0x0
+    field @Deprecated public static final long INFINITY = -1L; // 0xffffffffffffffffL
+    field @Deprecated public static final int LARGE_IMAGE = 2; // 0x2
+    field @Deprecated public static final int RANGE_MODE_DETERMINATE = 0; // 0x0
+    field @Deprecated public static final int RANGE_MODE_INDETERMINATE = 1; // 0x1
+    field @Deprecated public static final int RANGE_MODE_STAR_RATING = 2; // 0x2
+    field @Deprecated public static final int RAW_IMAGE_LARGE = 4; // 0x4
+    field @Deprecated public static final int RAW_IMAGE_SMALL = 3; // 0x3
+    field @Deprecated public static final int SMALL_IMAGE = 1; // 0x1
+    field @Deprecated public static final int UNKNOWN_IMAGE = 5; // 0x5
   }
 
-  public static class ListBuilder.HeaderBuilder {
-    ctor public ListBuilder.HeaderBuilder();
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setContentDescription(CharSequence);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setLayoutDirection(int);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(CharSequence, boolean);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setSummary(CharSequence);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setSummary(CharSequence, boolean);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(CharSequence, boolean);
+  @Deprecated public static class ListBuilder.HeaderBuilder {
+    ctor @Deprecated public ListBuilder.HeaderBuilder();
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setLayoutDirection(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(CharSequence, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setSummary(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setSummary(CharSequence, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(CharSequence, boolean);
   }
 
-  public static class ListBuilder.InputRangeBuilder {
-    ctor public ListBuilder.InputRangeBuilder();
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder addEndItem(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder addEndItem(androidx.slice.builders.SliceAction, boolean);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setContentDescription(CharSequence);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setInputAction(android.app.PendingIntent);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setInputAction(androidx.remotecallback.RemoteCallback);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setLayoutDirection(int);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setMax(int);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setMin(int);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setSubtitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setThumb(androidx.core.graphics.drawable.IconCompat);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setValue(int);
+  @Deprecated public static class ListBuilder.InputRangeBuilder {
+    ctor @Deprecated public ListBuilder.InputRangeBuilder();
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder addEndItem(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder addEndItem(androidx.slice.builders.SliceAction, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setInputAction(android.app.PendingIntent);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setInputAction(androidx.remotecallback.RemoteCallback);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setLayoutDirection(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setMax(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setMin(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setSubtitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setThumb(androidx.core.graphics.drawable.IconCompat);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setValue(int);
   }
 
-  public static class ListBuilder.RangeBuilder {
-    ctor public ListBuilder.RangeBuilder();
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setContentDescription(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setLayoutDirection(int);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setMax(int);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setMode(int);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setSubtitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setTitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setValue(int);
+  @Deprecated public static class ListBuilder.RangeBuilder {
+    ctor @Deprecated public ListBuilder.RangeBuilder();
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setLayoutDirection(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setMax(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setMode(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setSubtitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setTitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setValue(int);
   }
 
-  public static final class ListBuilder.RatingBuilder {
-    ctor public ListBuilder.RatingBuilder();
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setContentDescription(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setInputAction(android.app.PendingIntent);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setInputAction(androidx.remotecallback.RemoteCallback);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setMax(int);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setMin(int);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setSubtitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setTitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setValue(float);
+  @Deprecated public static final class ListBuilder.RatingBuilder {
+    ctor @Deprecated public ListBuilder.RatingBuilder();
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setInputAction(android.app.PendingIntent);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setInputAction(androidx.remotecallback.RemoteCallback);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setMax(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setMin(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setSubtitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setTitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setValue(float);
   }
 
-  public static class ListBuilder.RowBuilder {
-    ctor public ListBuilder.RowBuilder();
-    ctor public ListBuilder.RowBuilder(android.net.Uri);
-    method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.core.graphics.drawable.IconCompat, int);
-    method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.core.graphics.drawable.IconCompat?, int, boolean);
-    method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.slice.builders.SliceAction, boolean);
-    method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(long);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setContentDescription(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setEndOfSection(boolean);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setLayoutDirection(int);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setSubtitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setSubtitle(CharSequence?, boolean);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setTitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setTitle(CharSequence?, boolean);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat?, int, boolean);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.slice.builders.SliceAction, boolean);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(long);
+  @Deprecated public static class ListBuilder.RowBuilder {
+    ctor @Deprecated public ListBuilder.RowBuilder();
+    ctor @Deprecated public ListBuilder.RowBuilder(android.net.Uri);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.core.graphics.drawable.IconCompat, int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.core.graphics.drawable.IconCompat?, int, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.slice.builders.SliceAction, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(long);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setEndOfSection(boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setLayoutDirection(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setSubtitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setSubtitle(CharSequence?, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitle(CharSequence?, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat?, int, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.slice.builders.SliceAction, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(long);
   }
 
-  @RequiresApi(19) public class SelectionBuilder {
-    ctor public SelectionBuilder();
-    method public androidx.slice.builders.SelectionBuilder! addOption(String!, CharSequence!);
-    method public androidx.slice.builders.SelectionBuilder! setContentDescription(CharSequence?);
-    method public androidx.slice.builders.SelectionBuilder! setInputAction(android.app.PendingIntent);
-    method public androidx.slice.builders.SelectionBuilder! setInputAction(androidx.remotecallback.RemoteCallback);
-    method public androidx.slice.builders.SelectionBuilder! setLayoutDirection(int);
-    method public androidx.slice.builders.SelectionBuilder! setPrimaryAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.SelectionBuilder! setSelectedOption(String!);
-    method public androidx.slice.builders.SelectionBuilder! setSubtitle(CharSequence?);
-    method public androidx.slice.builders.SelectionBuilder! setTitle(CharSequence?);
+  @Deprecated @RequiresApi(19) public class SelectionBuilder {
+    ctor @Deprecated public SelectionBuilder();
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! addOption(String!, CharSequence!);
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! setContentDescription(CharSequence?);
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! setInputAction(android.app.PendingIntent);
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! setInputAction(androidx.remotecallback.RemoteCallback);
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! setLayoutDirection(int);
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! setPrimaryAction(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! setSelectedOption(String!);
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! setSubtitle(CharSequence?);
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! setTitle(CharSequence?);
   }
 
-  @RequiresApi(19) public class SliceAction implements androidx.slice.core.SliceAction {
-    method public static androidx.slice.builders.SliceAction! create(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
-    method public static androidx.slice.builders.SliceAction! create(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
-    method public static androidx.slice.builders.SliceAction! createDeeplink(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
-    method public static androidx.slice.builders.SliceAction! createDeeplink(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
-    method public static androidx.slice.builders.SliceAction! createToggle(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
-    method public static androidx.slice.builders.SliceAction! createToggle(android.app.PendingIntent, CharSequence, boolean);
-    method public static androidx.slice.builders.SliceAction! createToggle(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
-    method public static androidx.slice.builders.SliceAction! createToggle(androidx.remotecallback.RemoteCallback, CharSequence, boolean);
-    method public android.app.PendingIntent getAction();
-    method public CharSequence? getContentDescription();
-    method public androidx.core.graphics.drawable.IconCompat? getIcon();
-    method public int getImageMode();
-    method public String? getKey();
-    method public int getPriority();
-    method public CharSequence getTitle();
-    method public boolean isActivity();
-    method public boolean isChecked();
-    method public boolean isDefaultToggle();
-    method public boolean isToggle();
-    method public androidx.slice.builders.SliceAction setChecked(boolean);
-    method public androidx.slice.core.SliceAction setContentDescription(CharSequence);
-    method public androidx.slice.builders.SliceAction setKey(String);
-    method public androidx.slice.builders.SliceAction setPriority(@IntRange(from=0) int);
+  @Deprecated @RequiresApi(19) public class SliceAction implements androidx.slice.core.SliceAction {
+    method @Deprecated public static androidx.slice.builders.SliceAction! create(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+    method @Deprecated public static androidx.slice.builders.SliceAction! create(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+    method @Deprecated public static androidx.slice.builders.SliceAction! createDeeplink(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+    method @Deprecated public static androidx.slice.builders.SliceAction! createDeeplink(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+    method @Deprecated public static androidx.slice.builders.SliceAction! createToggle(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
+    method @Deprecated public static androidx.slice.builders.SliceAction! createToggle(android.app.PendingIntent, CharSequence, boolean);
+    method @Deprecated public static androidx.slice.builders.SliceAction! createToggle(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
+    method @Deprecated public static androidx.slice.builders.SliceAction! createToggle(androidx.remotecallback.RemoteCallback, CharSequence, boolean);
+    method @Deprecated public android.app.PendingIntent getAction();
+    method @Deprecated public CharSequence? getContentDescription();
+    method @Deprecated public androidx.core.graphics.drawable.IconCompat? getIcon();
+    method @Deprecated public int getImageMode();
+    method @Deprecated public String? getKey();
+    method @Deprecated public int getPriority();
+    method @Deprecated public CharSequence getTitle();
+    method @Deprecated public boolean isActivity();
+    method @Deprecated public boolean isChecked();
+    method @Deprecated public boolean isDefaultToggle();
+    method @Deprecated public boolean isToggle();
+    method @Deprecated public androidx.slice.builders.SliceAction setChecked(boolean);
+    method @Deprecated public androidx.slice.core.SliceAction setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.builders.SliceAction setKey(String);
+    method @Deprecated public androidx.slice.builders.SliceAction setPriority(@IntRange(from=0) int);
   }
 
-  @RequiresApi(19) public abstract class TemplateSliceBuilder {
-    method public androidx.slice.Slice build();
+  @Deprecated @RequiresApi(19) public abstract class TemplateSliceBuilder {
+    method @Deprecated public androidx.slice.Slice build();
   }
 
 }
diff --git a/slice/slice-builders/api/restricted_current.txt b/slice/slice-builders/api/restricted_current.txt
index 84c8575..fb90065 100644
--- a/slice/slice-builders/api/restricted_current.txt
+++ b/slice/slice-builders/api/restricted_current.txt
@@ -1,210 +1,210 @@
 // Signature format: 4.0
 package androidx.slice.builders {
 
-  @RequiresApi(19) public class GridRowBuilder {
-    ctor public GridRowBuilder();
-    method public androidx.slice.builders.GridRowBuilder addCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
-    method public androidx.slice.builders.GridRowBuilder setContentDescription(CharSequence);
-    method public androidx.slice.builders.GridRowBuilder setLayoutDirection(int);
-    method public androidx.slice.builders.GridRowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.GridRowBuilder setSeeMoreAction(android.app.PendingIntent);
-    method public androidx.slice.builders.GridRowBuilder setSeeMoreAction(androidx.remotecallback.RemoteCallback);
-    method public androidx.slice.builders.GridRowBuilder setSeeMoreCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
+  @Deprecated @RequiresApi(19) public class GridRowBuilder {
+    ctor @Deprecated public GridRowBuilder();
+    method @Deprecated public androidx.slice.builders.GridRowBuilder addCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder setLayoutDirection(int);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder setSeeMoreAction(android.app.PendingIntent);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder setSeeMoreAction(androidx.remotecallback.RemoteCallback);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder setSeeMoreCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
   }
 
-  public static class GridRowBuilder.CellBuilder {
-    ctor public GridRowBuilder.CellBuilder();
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(androidx.core.graphics.drawable.IconCompat, int);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(androidx.core.graphics.drawable.IconCompat?, int, boolean);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder addOverlayText(CharSequence);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder addOverlayText(CharSequence?, boolean);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder addText(CharSequence);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder addText(CharSequence?, boolean);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(CharSequence);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(CharSequence?, boolean);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder setContentDescription(CharSequence);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(android.app.PendingIntent);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(androidx.remotecallback.RemoteCallback);
-    method public androidx.slice.builders.GridRowBuilder.CellBuilder setSliceAction(androidx.slice.builders.SliceAction);
+  @Deprecated public static class GridRowBuilder.CellBuilder {
+    ctor @Deprecated public GridRowBuilder.CellBuilder();
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(androidx.core.graphics.drawable.IconCompat, int);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(androidx.core.graphics.drawable.IconCompat?, int, boolean);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addOverlayText(CharSequence);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addOverlayText(CharSequence?, boolean);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addText(CharSequence);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addText(CharSequence?, boolean);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(CharSequence);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(CharSequence?, boolean);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(android.app.PendingIntent);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(androidx.remotecallback.RemoteCallback);
+    method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder setSliceAction(androidx.slice.builders.SliceAction);
   }
 
-  @RequiresApi(19) public class ListBuilder extends androidx.slice.builders.TemplateSliceBuilder {
-    ctor @RequiresApi(26) public ListBuilder(android.content.Context, android.net.Uri, java.time.Duration?);
-    ctor public ListBuilder(android.content.Context, android.net.Uri, long);
-    method public androidx.slice.builders.ListBuilder addAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder addGridRow(androidx.slice.builders.GridRowBuilder);
-    method public androidx.slice.builders.ListBuilder addInputRange(androidx.slice.builders.ListBuilder.InputRangeBuilder);
-    method public androidx.slice.builders.ListBuilder addRange(androidx.slice.builders.ListBuilder.RangeBuilder);
-    method public androidx.slice.builders.ListBuilder addRating(androidx.slice.builders.ListBuilder.RatingBuilder);
-    method public androidx.slice.builders.ListBuilder addRow(androidx.slice.builders.ListBuilder.RowBuilder);
-    method public androidx.slice.builders.ListBuilder addSelection(androidx.slice.builders.SelectionBuilder);
-    method public androidx.slice.builders.ListBuilder setAccentColor(@ColorInt int);
-    method public androidx.slice.builders.ListBuilder setHeader(androidx.slice.builders.ListBuilder.HeaderBuilder);
-    method @RequiresApi(21) public androidx.slice.builders.ListBuilder setHostExtras(android.os.PersistableBundle);
-    method public androidx.slice.builders.ListBuilder setIsError(boolean);
-    method public androidx.slice.builders.ListBuilder setKeywords(java.util.Set<java.lang.String!>);
-    method public androidx.slice.builders.ListBuilder setLayoutDirection(int);
-    method public androidx.slice.builders.ListBuilder setSeeMoreAction(android.app.PendingIntent);
-    method public androidx.slice.builders.ListBuilder setSeeMoreAction(androidx.remotecallback.RemoteCallback);
-    method public androidx.slice.builders.ListBuilder setSeeMoreRow(androidx.slice.builders.ListBuilder.RowBuilder);
-    field public static final int ACTION_WITH_LABEL = 6; // 0x6
-    field public static final int ICON_IMAGE = 0; // 0x0
-    field public static final long INFINITY = -1L; // 0xffffffffffffffffL
-    field public static final int LARGE_IMAGE = 2; // 0x2
-    field public static final int RANGE_MODE_DETERMINATE = 0; // 0x0
-    field public static final int RANGE_MODE_INDETERMINATE = 1; // 0x1
-    field public static final int RANGE_MODE_STAR_RATING = 2; // 0x2
-    field public static final int RAW_IMAGE_LARGE = 4; // 0x4
-    field public static final int RAW_IMAGE_SMALL = 3; // 0x3
-    field public static final int SMALL_IMAGE = 1; // 0x1
-    field public static final int UNKNOWN_IMAGE = 5; // 0x5
+  @Deprecated @RequiresApi(19) public class ListBuilder extends androidx.slice.builders.TemplateSliceBuilder {
+    ctor @Deprecated @RequiresApi(26) public ListBuilder(android.content.Context, android.net.Uri, java.time.Duration?);
+    ctor @Deprecated public ListBuilder(android.content.Context, android.net.Uri, long);
+    method @Deprecated public androidx.slice.builders.ListBuilder addAction(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder addGridRow(androidx.slice.builders.GridRowBuilder);
+    method @Deprecated public androidx.slice.builders.ListBuilder addInputRange(androidx.slice.builders.ListBuilder.InputRangeBuilder);
+    method @Deprecated public androidx.slice.builders.ListBuilder addRange(androidx.slice.builders.ListBuilder.RangeBuilder);
+    method @Deprecated public androidx.slice.builders.ListBuilder addRating(androidx.slice.builders.ListBuilder.RatingBuilder);
+    method @Deprecated public androidx.slice.builders.ListBuilder addRow(androidx.slice.builders.ListBuilder.RowBuilder);
+    method @Deprecated public androidx.slice.builders.ListBuilder addSelection(androidx.slice.builders.SelectionBuilder);
+    method @Deprecated public androidx.slice.builders.ListBuilder setAccentColor(@ColorInt int);
+    method @Deprecated public androidx.slice.builders.ListBuilder setHeader(androidx.slice.builders.ListBuilder.HeaderBuilder);
+    method @Deprecated @RequiresApi(21) public androidx.slice.builders.ListBuilder setHostExtras(android.os.PersistableBundle);
+    method @Deprecated public androidx.slice.builders.ListBuilder setIsError(boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder setKeywords(java.util.Set<java.lang.String!>);
+    method @Deprecated public androidx.slice.builders.ListBuilder setLayoutDirection(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder setSeeMoreAction(android.app.PendingIntent);
+    method @Deprecated public androidx.slice.builders.ListBuilder setSeeMoreAction(androidx.remotecallback.RemoteCallback);
+    method @Deprecated public androidx.slice.builders.ListBuilder setSeeMoreRow(androidx.slice.builders.ListBuilder.RowBuilder);
+    field @Deprecated public static final int ACTION_WITH_LABEL = 6; // 0x6
+    field @Deprecated public static final int ICON_IMAGE = 0; // 0x0
+    field @Deprecated public static final long INFINITY = -1L; // 0xffffffffffffffffL
+    field @Deprecated public static final int LARGE_IMAGE = 2; // 0x2
+    field @Deprecated public static final int RANGE_MODE_DETERMINATE = 0; // 0x0
+    field @Deprecated public static final int RANGE_MODE_INDETERMINATE = 1; // 0x1
+    field @Deprecated public static final int RANGE_MODE_STAR_RATING = 2; // 0x2
+    field @Deprecated public static final int RAW_IMAGE_LARGE = 4; // 0x4
+    field @Deprecated public static final int RAW_IMAGE_SMALL = 3; // 0x3
+    field @Deprecated public static final int SMALL_IMAGE = 1; // 0x1
+    field @Deprecated public static final int UNKNOWN_IMAGE = 5; // 0x5
   }
 
-  public static class ListBuilder.HeaderBuilder {
-    ctor public ListBuilder.HeaderBuilder();
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public ListBuilder.HeaderBuilder(android.net.Uri);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setContentDescription(CharSequence);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setLayoutDirection(int);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(CharSequence, boolean);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setSummary(CharSequence);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setSummary(CharSequence, boolean);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(CharSequence, boolean);
+  @Deprecated public static class ListBuilder.HeaderBuilder {
+    ctor @Deprecated public ListBuilder.HeaderBuilder();
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public ListBuilder.HeaderBuilder(android.net.Uri);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setLayoutDirection(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(CharSequence, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setSummary(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setSummary(CharSequence, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(CharSequence, boolean);
   }
 
-  public static class ListBuilder.InputRangeBuilder {
-    ctor public ListBuilder.InputRangeBuilder();
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder addEndItem(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder addEndItem(androidx.slice.builders.SliceAction, boolean);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setContentDescription(CharSequence);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setInputAction(android.app.PendingIntent);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setInputAction(androidx.remotecallback.RemoteCallback);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setLayoutDirection(int);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setMax(int);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setMin(int);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setSubtitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setThumb(androidx.core.graphics.drawable.IconCompat);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
-    method public androidx.slice.builders.ListBuilder.InputRangeBuilder setValue(int);
+  @Deprecated public static class ListBuilder.InputRangeBuilder {
+    ctor @Deprecated public ListBuilder.InputRangeBuilder();
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder addEndItem(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder addEndItem(androidx.slice.builders.SliceAction, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setInputAction(android.app.PendingIntent);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setInputAction(androidx.remotecallback.RemoteCallback);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setLayoutDirection(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setMax(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setMin(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setSubtitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setThumb(androidx.core.graphics.drawable.IconCompat);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setValue(int);
   }
 
-  public static class ListBuilder.RangeBuilder {
-    ctor public ListBuilder.RangeBuilder();
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setContentDescription(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setLayoutDirection(int);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setMax(int);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setMode(int);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setSubtitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setTitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
-    method public androidx.slice.builders.ListBuilder.RangeBuilder setValue(int);
+  @Deprecated public static class ListBuilder.RangeBuilder {
+    ctor @Deprecated public ListBuilder.RangeBuilder();
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setLayoutDirection(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setMax(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setMode(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setSubtitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setTitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setValue(int);
   }
 
-  public static final class ListBuilder.RatingBuilder {
-    ctor public ListBuilder.RatingBuilder();
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setContentDescription(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setInputAction(android.app.PendingIntent);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setInputAction(androidx.remotecallback.RemoteCallback);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setMax(int);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setMin(int);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setSubtitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setTitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
-    method public androidx.slice.builders.ListBuilder.RatingBuilder setValue(float);
+  @Deprecated public static final class ListBuilder.RatingBuilder {
+    ctor @Deprecated public ListBuilder.RatingBuilder();
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setInputAction(android.app.PendingIntent);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setInputAction(androidx.remotecallback.RemoteCallback);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setMax(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setMin(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setSubtitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setTitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setValue(float);
   }
 
-  public static class ListBuilder.RowBuilder {
-    ctor public ListBuilder.RowBuilder();
-    ctor public ListBuilder.RowBuilder(android.net.Uri);
-    method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.core.graphics.drawable.IconCompat, int);
-    method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.core.graphics.drawable.IconCompat?, int, boolean);
-    method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.slice.builders.SliceAction, boolean);
-    method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(long);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setContentDescription(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setEndOfSection(boolean);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setLayoutDirection(int);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setSubtitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setSubtitle(CharSequence?, boolean);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setTitle(CharSequence);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setTitle(CharSequence?, boolean);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat?, int, boolean);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.slice.builders.SliceAction, boolean);
-    method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(long);
+  @Deprecated public static class ListBuilder.RowBuilder {
+    ctor @Deprecated public ListBuilder.RowBuilder();
+    ctor @Deprecated public ListBuilder.RowBuilder(android.net.Uri);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.core.graphics.drawable.IconCompat, int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.core.graphics.drawable.IconCompat?, int, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.slice.builders.SliceAction, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(long);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setEndOfSection(boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setLayoutDirection(int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setSubtitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setSubtitle(CharSequence?, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitle(CharSequence);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitle(CharSequence?, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat?, int, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.slice.builders.SliceAction, boolean);
+    method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(long);
   }
 
-  @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class MessagingSliceBuilder extends androidx.slice.builders.TemplateSliceBuilder {
-    ctor public MessagingSliceBuilder(android.content.Context, android.net.Uri);
-    method public androidx.slice.builders.MessagingSliceBuilder! add(androidx.core.util.Consumer<androidx.slice.builders.MessagingSliceBuilder.MessageBuilder!>!);
-    method public androidx.slice.builders.MessagingSliceBuilder! add(androidx.slice.builders.MessagingSliceBuilder.MessageBuilder!);
-    field public static final int MAXIMUM_RETAINED_MESSAGES = 50; // 0x32
+  @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class MessagingSliceBuilder extends androidx.slice.builders.TemplateSliceBuilder {
+    ctor @Deprecated public MessagingSliceBuilder(android.content.Context, android.net.Uri);
+    method @Deprecated public androidx.slice.builders.MessagingSliceBuilder! add(androidx.core.util.Consumer<androidx.slice.builders.MessagingSliceBuilder.MessageBuilder!>!);
+    method @Deprecated public androidx.slice.builders.MessagingSliceBuilder! add(androidx.slice.builders.MessagingSliceBuilder.MessageBuilder!);
+    field @Deprecated public static final int MAXIMUM_RETAINED_MESSAGES = 50; // 0x32
   }
 
-  public static final class MessagingSliceBuilder.MessageBuilder extends androidx.slice.builders.TemplateSliceBuilder {
-    ctor public MessagingSliceBuilder.MessageBuilder(androidx.slice.builders.MessagingSliceBuilder!);
-    method @RequiresApi(23) public androidx.slice.builders.MessagingSliceBuilder.MessageBuilder! addSource(android.graphics.drawable.Icon!);
-    method public androidx.slice.builders.MessagingSliceBuilder.MessageBuilder! addSource(androidx.core.graphics.drawable.IconCompat!);
-    method public androidx.slice.builders.MessagingSliceBuilder.MessageBuilder! addText(CharSequence!);
-    method public androidx.slice.builders.MessagingSliceBuilder.MessageBuilder! addTimestamp(long);
+  @Deprecated public static final class MessagingSliceBuilder.MessageBuilder extends androidx.slice.builders.TemplateSliceBuilder {
+    ctor @Deprecated public MessagingSliceBuilder.MessageBuilder(androidx.slice.builders.MessagingSliceBuilder!);
+    method @Deprecated @RequiresApi(23) public androidx.slice.builders.MessagingSliceBuilder.MessageBuilder! addSource(android.graphics.drawable.Icon!);
+    method @Deprecated public androidx.slice.builders.MessagingSliceBuilder.MessageBuilder! addSource(androidx.core.graphics.drawable.IconCompat!);
+    method @Deprecated public androidx.slice.builders.MessagingSliceBuilder.MessageBuilder! addText(CharSequence!);
+    method @Deprecated public androidx.slice.builders.MessagingSliceBuilder.MessageBuilder! addTimestamp(long);
   }
 
-  @RequiresApi(19) public class SelectionBuilder {
-    ctor public SelectionBuilder();
-    method public androidx.slice.builders.SelectionBuilder! addOption(String!, CharSequence!);
-    method public androidx.slice.builders.SelectionBuilder! setContentDescription(CharSequence?);
-    method public androidx.slice.builders.SelectionBuilder! setInputAction(android.app.PendingIntent);
-    method public androidx.slice.builders.SelectionBuilder! setInputAction(androidx.remotecallback.RemoteCallback);
-    method public androidx.slice.builders.SelectionBuilder! setLayoutDirection(int);
-    method public androidx.slice.builders.SelectionBuilder! setPrimaryAction(androidx.slice.builders.SliceAction);
-    method public androidx.slice.builders.SelectionBuilder! setSelectedOption(String!);
-    method public androidx.slice.builders.SelectionBuilder! setSubtitle(CharSequence?);
-    method public androidx.slice.builders.SelectionBuilder! setTitle(CharSequence?);
+  @Deprecated @RequiresApi(19) public class SelectionBuilder {
+    ctor @Deprecated public SelectionBuilder();
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! addOption(String!, CharSequence!);
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! setContentDescription(CharSequence?);
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! setInputAction(android.app.PendingIntent);
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! setInputAction(androidx.remotecallback.RemoteCallback);
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! setLayoutDirection(int);
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! setPrimaryAction(androidx.slice.builders.SliceAction);
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! setSelectedOption(String!);
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! setSubtitle(CharSequence?);
+    method @Deprecated public androidx.slice.builders.SelectionBuilder! setTitle(CharSequence?);
   }
 
-  @RequiresApi(19) public class SliceAction implements androidx.slice.core.SliceAction {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceAction(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceAction(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceAction(android.app.PendingIntent, CharSequence, boolean);
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceAction(android.app.PendingIntent, CharSequence, long, boolean);
-    method public static androidx.slice.builders.SliceAction! create(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
-    method public static androidx.slice.builders.SliceAction! create(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
-    method public static androidx.slice.builders.SliceAction! createDeeplink(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
-    method public static androidx.slice.builders.SliceAction! createDeeplink(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
-    method public static androidx.slice.builders.SliceAction! createToggle(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
-    method public static androidx.slice.builders.SliceAction! createToggle(android.app.PendingIntent, CharSequence, boolean);
-    method public static androidx.slice.builders.SliceAction! createToggle(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
-    method public static androidx.slice.builders.SliceAction! createToggle(androidx.remotecallback.RemoteCallback, CharSequence, boolean);
-    method public android.app.PendingIntent getAction();
-    method public CharSequence? getContentDescription();
-    method public androidx.core.graphics.drawable.IconCompat? getIcon();
-    method public int getImageMode();
-    method public String? getKey();
-    method public int getPriority();
-    method public CharSequence getTitle();
-    method public boolean isActivity();
-    method public boolean isChecked();
-    method public boolean isDefaultToggle();
-    method public boolean isToggle();
-    method public androidx.slice.builders.SliceAction setChecked(boolean);
-    method public androidx.slice.core.SliceAction setContentDescription(CharSequence);
-    method public androidx.slice.builders.SliceAction setKey(String);
-    method public androidx.slice.builders.SliceAction setPriority(@IntRange(from=0) int);
+  @Deprecated @RequiresApi(19) public class SliceAction implements androidx.slice.core.SliceAction {
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceAction(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceAction(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceAction(android.app.PendingIntent, CharSequence, boolean);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceAction(android.app.PendingIntent, CharSequence, long, boolean);
+    method @Deprecated public static androidx.slice.builders.SliceAction! create(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+    method @Deprecated public static androidx.slice.builders.SliceAction! create(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+    method @Deprecated public static androidx.slice.builders.SliceAction! createDeeplink(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+    method @Deprecated public static androidx.slice.builders.SliceAction! createDeeplink(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+    method @Deprecated public static androidx.slice.builders.SliceAction! createToggle(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
+    method @Deprecated public static androidx.slice.builders.SliceAction! createToggle(android.app.PendingIntent, CharSequence, boolean);
+    method @Deprecated public static androidx.slice.builders.SliceAction! createToggle(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
+    method @Deprecated public static androidx.slice.builders.SliceAction! createToggle(androidx.remotecallback.RemoteCallback, CharSequence, boolean);
+    method @Deprecated public android.app.PendingIntent getAction();
+    method @Deprecated public CharSequence? getContentDescription();
+    method @Deprecated public androidx.core.graphics.drawable.IconCompat? getIcon();
+    method @Deprecated public int getImageMode();
+    method @Deprecated public String? getKey();
+    method @Deprecated public int getPriority();
+    method @Deprecated public CharSequence getTitle();
+    method @Deprecated public boolean isActivity();
+    method @Deprecated public boolean isChecked();
+    method @Deprecated public boolean isDefaultToggle();
+    method @Deprecated public boolean isToggle();
+    method @Deprecated public androidx.slice.builders.SliceAction setChecked(boolean);
+    method @Deprecated public androidx.slice.core.SliceAction setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.builders.SliceAction setKey(String);
+    method @Deprecated public androidx.slice.builders.SliceAction setPriority(@IntRange(from=0) int);
   }
 
-  @RequiresApi(19) public abstract class TemplateSliceBuilder {
-    method public androidx.slice.Slice build();
+  @Deprecated @RequiresApi(19) public abstract class TemplateSliceBuilder {
+    method @Deprecated public androidx.slice.Slice build();
   }
 
 }
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/GridRowBuilder.java b/slice/slice-builders/src/main/java/androidx/slice/builders/GridRowBuilder.java
index 4523685..dddc854 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/GridRowBuilder.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/GridRowBuilder.java
@@ -53,8 +53,13 @@
  * rest of the content, this will take up space as a cell item in a row if added.
  *
  * @see ListBuilder#addGridRow(GridRowBuilder)
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public class GridRowBuilder {
 
     private final List<CellBuilder> mCells = new ArrayList<>();
@@ -256,7 +261,12 @@
      * @see ListBuilder#ICON_IMAGE
      * @see ListBuilder#SMALL_IMAGE
      * @see ListBuilder#ICON_IMAGE
+     *
+     * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+     * forward. If you are looking for a framework that handles communication across apps,
+     * consider using {@link android.app.appsearch.AppSearchManager}.
      */
+    @Deprecated
     public static class CellBuilder {
         /**
          */
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/ListBuilder.java b/slice/slice-builders/src/main/java/androidx/slice/builders/ListBuilder.java
index 77d5f5b..1b27322 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/ListBuilder.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/ListBuilder.java
@@ -140,8 +140,13 @@
  * @see androidx.slice.SliceProvider
  * @see androidx.slice.SliceProvider#onBindSlice(Uri)
  * @see androidx.slice.widget.SliceView
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public class ListBuilder extends TemplateSliceBuilder {
 
     private boolean mHasSeeMore;
@@ -565,7 +570,12 @@
      * A range row supports displaying a horizontal progress indicator.
      *
      * @see ListBuilder#addRange(RangeBuilder)
+     *
+     * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+     * forward. If you are looking for a framework that handles communication across apps,
+     * consider using {@link android.app.appsearch.AppSearchManager}.
      */
+    @Deprecated
     public static class RangeBuilder {
 
         private int mValue;
@@ -817,8 +827,13 @@
      * An star rating row supports displaying a horizontal tappable stars allowing rating input.
      *
      * @see ListBuilder#addRating(RatingBuilder)
+     *
+     * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+     * forward. If you are looking for a framework that handles communication across apps,
+     * consider using {@link android.app.appsearch.AppSearchManager}.
      */
     @SuppressLint("MissingBuildMethod")
+    @Deprecated
     public static final class RatingBuilder {
         /**
          */
@@ -1085,7 +1100,12 @@
      * An input range row supports displaying a horizontal slider allowing slider input.
      *
      * @see ListBuilder#addInputRange(InputRangeBuilder)
+     *
+     * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+     * forward. If you are looking for a framework that handles communication across apps,
+     * consider using {@link android.app.appsearch.AppSearchManager}.
      */
+    @Deprecated
     public static class InputRangeBuilder {
 
         private int mMin = 0;
@@ -1481,7 +1501,12 @@
      * </ul>
      *
      * @see ListBuilder#addRow(RowBuilder)
+     *
+     * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+     * forward. If you are looking for a framework that handles communication across apps,
+     * consider using {@link android.app.appsearch.AppSearchManager}.
      */
+    @Deprecated
     public static class RowBuilder {
 
         private final Uri mUri;
@@ -2007,7 +2032,12 @@
      * @see ListBuilder#setHeader(HeaderBuilder)
      * @see ListBuilder#addAction(SliceAction)
      * @see SliceAction
+     *
+     * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+     * forward. If you are looking for a framework that handles communication across apps,
+     * consider using {@link android.app.appsearch.AppSearchManager}.
      */
+    @Deprecated
     public static class HeaderBuilder {
         private final Uri mUri;
         private CharSequence mTitle;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/MessagingSliceBuilder.java b/slice/slice-builders/src/main/java/androidx/slice/builders/MessagingSliceBuilder.java
index 4041d50..b995566 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/MessagingSliceBuilder.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/MessagingSliceBuilder.java
@@ -41,6 +41,7 @@
  */
 @RestrictTo(LIBRARY_GROUP_PREFIX)
 @RequiresApi(19)
+@Deprecated
 public class MessagingSliceBuilder extends TemplateSliceBuilder {
 
     /**
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/SelectionBuilder.java b/slice/slice-builders/src/main/java/androidx/slice/builders/SelectionBuilder.java
index 4e5550a..1e72a08 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/SelectionBuilder.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/SelectionBuilder.java
@@ -38,8 +38,13 @@
  *
  * A selection presents a list of options to the user and allows the user to select exactly one
  * option.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public class SelectionBuilder {
     private final List<Pair<String, CharSequence>> mOptions;
     private final Set<String> mOptionKeys;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/SliceAction.java b/slice/slice-builders/src/main/java/androidx/slice/builders/SliceAction.java
index 64263dd..bc06970 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/SliceAction.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/SliceAction.java
@@ -36,8 +36,13 @@
 /**
  * Class representing an action, supports tappable icons, custom toggle icons, and default
  * toggles, as well as date and time pickers.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public class SliceAction implements androidx.slice.core.SliceAction {
 
     private final SliceActionImpl mSliceAction;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/TemplateSliceBuilder.java b/slice/slice-builders/src/main/java/androidx/slice/builders/TemplateSliceBuilder.java
index 07accb6..f52b6d1 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/TemplateSliceBuilder.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/TemplateSliceBuilder.java
@@ -39,8 +39,13 @@
 
 /**
  * Base class of builders of various template types.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public abstract class TemplateSliceBuilder {
 
     private static final String TAG = "TemplateSliceBuilder";
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java
index ec399bc..f7baa96 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java
@@ -45,6 +45,7 @@
  */
 @RestrictTo(LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class GridRowBuilderListV1Impl extends TemplateBuilderImpl {
 
     private SliceAction mPrimaryAction;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java
index 8718ac9..d9f47d6 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java
@@ -42,6 +42,7 @@
  */
 @RestrictTo(LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public interface ListBuilder {
 
     /**
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java
index faf5588..5848e3b 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java
@@ -60,6 +60,7 @@
  */
 @RestrictTo(LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class ListBuilderBasicImpl extends TemplateBuilderImpl implements ListBuilder {
     boolean mIsError;
     private Set<String> mKeywords;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilderImpl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilderImpl.java
index 111bd0b..684f094 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilderImpl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilderImpl.java
@@ -85,6 +85,7 @@
  */
 @RestrictTo(LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class ListBuilderImpl extends TemplateBuilderImpl implements ListBuilder {
     private List<Slice> mSliceActions;
     private Set<String> mKeywords;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingBasicImpl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingBasicImpl.java
index 239e6fb..834b44a 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingBasicImpl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingBasicImpl.java
@@ -37,6 +37,7 @@
  */
 @RestrictTo(LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class MessagingBasicImpl extends TemplateBuilderImpl implements
         MessagingBuilder {
     private MessageBuilder mLastMessage;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingBuilder.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingBuilder.java
index 03f48ce..3741273 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingBuilder.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingBuilder.java
@@ -27,6 +27,7 @@
  */
 @RestrictTo(LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public interface MessagingBuilder {
     /**
      * Add a subslice to this builder.
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingListV1Impl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingListV1Impl.java
index 8dacf8f..90a82f7 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingListV1Impl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingListV1Impl.java
@@ -33,6 +33,7 @@
  */
 @RestrictTo(LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class MessagingListV1Impl extends TemplateBuilderImpl implements MessagingBuilder{
 
     private final ListBuilderImpl mListBuilder;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingV1Impl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingV1Impl.java
index d913d79..0adf579 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingV1Impl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingV1Impl.java
@@ -31,6 +31,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class MessagingV1Impl extends TemplateBuilderImpl implements MessagingBuilder {
 
     /**
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderBasicImpl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderBasicImpl.java
index 1c341f1..9dc3fca 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderBasicImpl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderBasicImpl.java
@@ -33,6 +33,7 @@
  */
 @RestrictTo(LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class SelectionBuilderBasicImpl extends SelectionBuilderImpl {
     public SelectionBuilderBasicImpl(Slice.Builder sliceBuilder,
                                      SelectionBuilder selectionBuilder) {
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderImpl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderImpl.java
index f25d0a6..0647caf 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderImpl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderImpl.java
@@ -28,6 +28,7 @@
  */
 @RestrictTo(LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public abstract class SelectionBuilderImpl extends TemplateBuilderImpl {
     private final SelectionBuilder mSelectionBuilder;
 
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderListV2Impl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderListV2Impl.java
index 0c25ad0..1d2d673 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderListV2Impl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderListV2Impl.java
@@ -41,6 +41,7 @@
  */
 @RestrictTo(LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class SelectionBuilderListV2Impl extends SelectionBuilderImpl {
     public SelectionBuilderListV2Impl(Slice.Builder parentSliceBuilder,
                                   SelectionBuilder selectionBuilder) {
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java
index 8cb26f66..a1be533 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java
@@ -43,6 +43,7 @@
  */
 @RestrictTo(LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public abstract class TemplateBuilderImpl {
 
     private Slice.Builder mSliceBuilder;
diff --git a/slice/slice-core/api/current.txt b/slice/slice-core/api/current.txt
index 6a6b471..bc4b051 100644
--- a/slice/slice-core/api/current.txt
+++ b/slice/slice-core/api/current.txt
@@ -1,87 +1,87 @@
 // Signature format: 4.0
 package androidx.slice {
 
-  @RequiresApi(19) public final class Slice implements androidx.versionedparcelable.VersionedParcelable {
-    method public java.util.List<java.lang.String!> getHints();
-    method public java.util.List<androidx.slice.SliceItem!> getItems();
-    method public android.net.Uri getUri();
-    field public static final String EXTRA_SELECTION = "android.app.slice.extra.SELECTION";
+  @Deprecated @RequiresApi(19) public final class Slice implements androidx.versionedparcelable.VersionedParcelable {
+    method @Deprecated public java.util.List<java.lang.String!> getHints();
+    method @Deprecated public java.util.List<androidx.slice.SliceItem!> getItems();
+    method @Deprecated public android.net.Uri getUri();
+    field @Deprecated public static final String EXTRA_SELECTION = "android.app.slice.extra.SELECTION";
   }
 
-  @RequiresApi(28) public class SliceConvert {
-    method public static android.app.slice.Slice? unwrap(androidx.slice.Slice?);
-    method public static androidx.slice.Slice? wrap(android.app.slice.Slice?, android.content.Context);
+  @Deprecated @RequiresApi(28) public class SliceConvert {
+    method @Deprecated public static android.app.slice.Slice? unwrap(androidx.slice.Slice?);
+    method @Deprecated public static androidx.slice.Slice? wrap(android.app.slice.Slice?, android.content.Context);
   }
 
-  @RequiresApi(19) public final class SliceItem implements androidx.versionedparcelable.VersionedParcelable {
-    method public static android.text.ParcelableSpan createSensitiveSpan();
-    method public void fireAction(android.content.Context?, android.content.Intent?) throws android.app.PendingIntent.CanceledException;
-    method public android.app.PendingIntent? getAction();
-    method public String getFormat();
-    method public java.util.List<java.lang.String!> getHints();
-    method public androidx.core.graphics.drawable.IconCompat? getIcon();
-    method public int getInt();
-    method public long getLong();
-    method public CharSequence? getRedactedText();
-    method public androidx.slice.Slice? getSlice();
-    method public String? getSubType();
-    method public CharSequence? getText();
-    method public boolean hasHint(String);
-    method public void onPostParceling();
-    method public void onPreParceling(boolean);
+  @Deprecated @RequiresApi(19) public final class SliceItem implements androidx.versionedparcelable.VersionedParcelable {
+    method @Deprecated public static android.text.ParcelableSpan createSensitiveSpan();
+    method @Deprecated public void fireAction(android.content.Context?, android.content.Intent?) throws android.app.PendingIntent.CanceledException;
+    method @Deprecated public android.app.PendingIntent? getAction();
+    method @Deprecated public String getFormat();
+    method @Deprecated public java.util.List<java.lang.String!> getHints();
+    method @Deprecated public androidx.core.graphics.drawable.IconCompat? getIcon();
+    method @Deprecated public int getInt();
+    method @Deprecated public long getLong();
+    method @Deprecated public CharSequence? getRedactedText();
+    method @Deprecated public androidx.slice.Slice? getSlice();
+    method @Deprecated public String? getSubType();
+    method @Deprecated public CharSequence? getText();
+    method @Deprecated public boolean hasHint(String);
+    method @Deprecated public void onPostParceling();
+    method @Deprecated public void onPreParceling(boolean);
   }
 
-  @RequiresApi(19) public abstract class SliceManager {
-    method public abstract int checkSlicePermission(android.net.Uri, int, int);
-    method public static androidx.slice.SliceManager getInstance(android.content.Context);
-    method public abstract java.util.List<android.net.Uri!> getPinnedSlices();
-    method public abstract void grantSlicePermission(String, android.net.Uri);
-    method public abstract void revokeSlicePermission(String, android.net.Uri);
+  @Deprecated @RequiresApi(19) public abstract class SliceManager {
+    method @Deprecated public abstract int checkSlicePermission(android.net.Uri, int, int);
+    method @Deprecated public static androidx.slice.SliceManager getInstance(android.content.Context);
+    method @Deprecated public abstract java.util.List<android.net.Uri!> getPinnedSlices();
+    method @Deprecated public abstract void grantSlicePermission(String, android.net.Uri);
+    method @Deprecated public abstract void revokeSlicePermission(String, android.net.Uri);
   }
 
-  public abstract class SliceProvider extends android.content.ContentProvider {
-    ctor public SliceProvider();
-    ctor public SliceProvider(java.lang.String!...);
-    method public final int bulkInsert(android.net.Uri, android.content.ContentValues![]);
-    method @RequiresApi(19) public final android.net.Uri? canonicalize(android.net.Uri);
-    method public final int delete(android.net.Uri, String?, String![]?);
-    method @RequiresApi(19) public java.util.List<android.net.Uri!> getPinnedSlices();
-    method public final String? getType(android.net.Uri);
-    method public final android.net.Uri? insert(android.net.Uri, android.content.ContentValues?);
-    method @RequiresApi(19) public abstract androidx.slice.Slice? onBindSlice(android.net.Uri);
-    method public final boolean onCreate();
-    method public android.app.PendingIntent? onCreatePermissionRequest(android.net.Uri, String);
-    method @RequiresApi(19) public abstract boolean onCreateSliceProvider();
-    method @RequiresApi(19) public java.util.Collection<android.net.Uri!> onGetSliceDescendants(android.net.Uri);
-    method @RequiresApi(19) public android.net.Uri onMapIntentToUri(android.content.Intent);
-    method @RequiresApi(19) public void onSlicePinned(android.net.Uri);
-    method @RequiresApi(19) public void onSliceUnpinned(android.net.Uri);
-    method @RequiresApi(28) public final android.database.Cursor? query(android.net.Uri, String![]?, android.os.Bundle?, android.os.CancellationSignal?);
-    method public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?);
-    method @RequiresApi(16) public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?, android.os.CancellationSignal?);
-    method public final int update(android.net.Uri, android.content.ContentValues?, String?, String![]?);
+  @Deprecated public abstract class SliceProvider extends android.content.ContentProvider {
+    ctor @Deprecated public SliceProvider();
+    ctor @Deprecated public SliceProvider(java.lang.String!...);
+    method @Deprecated public final int bulkInsert(android.net.Uri, android.content.ContentValues![]);
+    method @Deprecated @RequiresApi(19) public final android.net.Uri? canonicalize(android.net.Uri);
+    method @Deprecated public final int delete(android.net.Uri, String?, String![]?);
+    method @Deprecated @RequiresApi(19) public java.util.List<android.net.Uri!> getPinnedSlices();
+    method @Deprecated public final String? getType(android.net.Uri);
+    method @Deprecated public final android.net.Uri? insert(android.net.Uri, android.content.ContentValues?);
+    method @Deprecated @RequiresApi(19) public abstract androidx.slice.Slice? onBindSlice(android.net.Uri);
+    method @Deprecated public final boolean onCreate();
+    method @Deprecated public android.app.PendingIntent? onCreatePermissionRequest(android.net.Uri, String);
+    method @Deprecated @RequiresApi(19) public abstract boolean onCreateSliceProvider();
+    method @Deprecated @RequiresApi(19) public java.util.Collection<android.net.Uri!> onGetSliceDescendants(android.net.Uri);
+    method @Deprecated @RequiresApi(19) public android.net.Uri onMapIntentToUri(android.content.Intent);
+    method @Deprecated @RequiresApi(19) public void onSlicePinned(android.net.Uri);
+    method @Deprecated @RequiresApi(19) public void onSliceUnpinned(android.net.Uri);
+    method @Deprecated @RequiresApi(28) public final android.database.Cursor? query(android.net.Uri, String![]?, android.os.Bundle?, android.os.CancellationSignal?);
+    method @Deprecated public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?);
+    method @Deprecated @RequiresApi(16) public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?, android.os.CancellationSignal?);
+    method @Deprecated public final int update(android.net.Uri, android.content.ContentValues?, String?, String![]?);
   }
 
 }
 
 package androidx.slice.core {
 
-  @RequiresApi(19) public interface SliceAction {
-    method public android.app.PendingIntent getAction();
-    method public CharSequence? getContentDescription();
-    method public androidx.core.graphics.drawable.IconCompat? getIcon();
-    method public int getImageMode();
-    method public String? getKey();
-    method public int getPriority();
-    method public CharSequence getTitle();
-    method public boolean isActivity();
-    method public boolean isChecked();
-    method public boolean isDefaultToggle();
-    method public boolean isToggle();
-    method public androidx.slice.core.SliceAction setChecked(boolean);
-    method public androidx.slice.core.SliceAction setContentDescription(CharSequence);
-    method public androidx.slice.core.SliceAction setKey(String);
-    method public androidx.slice.core.SliceAction setPriority(@IntRange(from=0) int);
+  @Deprecated @RequiresApi(19) public interface SliceAction {
+    method @Deprecated public android.app.PendingIntent getAction();
+    method @Deprecated public CharSequence? getContentDescription();
+    method @Deprecated public androidx.core.graphics.drawable.IconCompat? getIcon();
+    method @Deprecated public int getImageMode();
+    method @Deprecated public String? getKey();
+    method @Deprecated public int getPriority();
+    method @Deprecated public CharSequence getTitle();
+    method @Deprecated public boolean isActivity();
+    method @Deprecated public boolean isChecked();
+    method @Deprecated public boolean isDefaultToggle();
+    method @Deprecated public boolean isToggle();
+    method @Deprecated public androidx.slice.core.SliceAction setChecked(boolean);
+    method @Deprecated public androidx.slice.core.SliceAction setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.core.SliceAction setKey(String);
+    method @Deprecated public androidx.slice.core.SliceAction setPriority(@IntRange(from=0) int);
   }
 
 }
diff --git a/slice/slice-core/api/restricted_current.txt b/slice/slice-core/api/restricted_current.txt
index 332afef..b000782 100644
--- a/slice/slice-core/api/restricted_current.txt
+++ b/slice/slice-core/api/restricted_current.txt
@@ -1,307 +1,307 @@
 // Signature format: 4.0
 package androidx.slice {
 
-  @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface Clock {
-    method public long currentTimeMillis();
+  @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface Clock {
+    method @Deprecated public long currentTimeMillis();
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class CornerDrawable extends android.graphics.drawable.InsetDrawable {
-    ctor public CornerDrawable(android.graphics.drawable.Drawable?, float);
+  @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class CornerDrawable extends android.graphics.drawable.InsetDrawable {
+    ctor @Deprecated public CornerDrawable(android.graphics.drawable.Drawable?, float);
   }
 
-  @RequiresApi(19) @androidx.versionedparcelable.VersionedParcelize(allowSerialization=true, isCustom=true) public final class Slice extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.versionedparcelable.VersionedParcelable {
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.slice.Slice? bindSlice(android.content.Context, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>?);
-    method public java.util.List<java.lang.String!> getHints();
-    method public java.util.List<androidx.slice.SliceItem!> getItems();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.SliceSpec? getSpec();
-    method public android.net.Uri getUri();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean hasHint(String);
-    field public static final String EXTRA_SELECTION = "android.app.slice.extra.SELECTION";
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String SUBTYPE_RANGE_MODE = "range_mode";
+  @Deprecated @RequiresApi(19) @androidx.versionedparcelable.VersionedParcelize(allowSerialization=true, isCustom=true) public final class Slice extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.versionedparcelable.VersionedParcelable {
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.slice.Slice? bindSlice(android.content.Context, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>?);
+    method @Deprecated public java.util.List<java.lang.String!> getHints();
+    method @Deprecated public java.util.List<androidx.slice.SliceItem!> getItems();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.SliceSpec? getSpec();
+    method @Deprecated public android.net.Uri getUri();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean hasHint(String);
+    field @Deprecated public static final String EXTRA_SELECTION = "android.app.slice.extra.SELECTION";
+    field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String SUBTYPE_RANGE_MODE = "range_mode";
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class Slice.Builder {
-    ctor public Slice.Builder(android.net.Uri);
-    ctor public Slice.Builder(androidx.slice.Slice.Builder);
-    method public androidx.slice.Slice.Builder addAction(android.app.PendingIntent, androidx.slice.Slice, String?);
-    method public androidx.slice.Slice.Builder addAction(androidx.slice.Slice, String?, androidx.slice.SliceItem.ActionHandler);
-    method public androidx.slice.Slice.Builder addHints(java.lang.String!...);
-    method public androidx.slice.Slice.Builder addHints(java.util.List<java.lang.String!>);
-    method public androidx.slice.Slice.Builder addIcon(androidx.core.graphics.drawable.IconCompat, String?, java.lang.String!...);
-    method public androidx.slice.Slice.Builder addIcon(androidx.core.graphics.drawable.IconCompat, String?, java.util.List<java.lang.String!>);
-    method public androidx.slice.Slice.Builder addInt(int, String?, java.lang.String!...);
-    method public androidx.slice.Slice.Builder addInt(int, String?, java.util.List<java.lang.String!>);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public androidx.slice.Slice.Builder addItem(androidx.slice.SliceItem);
-    method public androidx.slice.Slice.Builder addLong(long, String?, java.lang.String!...);
-    method public androidx.slice.Slice.Builder addLong(long, String?, java.util.List<java.lang.String!>);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, String?, java.lang.String!...);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, String?, java.util.List<java.lang.String!>);
-    method public androidx.slice.Slice.Builder addSubSlice(androidx.slice.Slice);
-    method public androidx.slice.Slice.Builder addSubSlice(androidx.slice.Slice, String?);
-    method public androidx.slice.Slice.Builder addText(CharSequence?, String?, java.lang.String!...);
-    method public androidx.slice.Slice.Builder addText(CharSequence?, String?, java.util.List<java.lang.String!>);
+  @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class Slice.Builder {
+    ctor @Deprecated public Slice.Builder(android.net.Uri);
+    ctor @Deprecated public Slice.Builder(androidx.slice.Slice.Builder);
+    method @Deprecated public androidx.slice.Slice.Builder addAction(android.app.PendingIntent, androidx.slice.Slice, String?);
+    method @Deprecated public androidx.slice.Slice.Builder addAction(androidx.slice.Slice, String?, androidx.slice.SliceItem.ActionHandler);
+    method @Deprecated public androidx.slice.Slice.Builder addHints(java.lang.String!...);
+    method @Deprecated public androidx.slice.Slice.Builder addHints(java.util.List<java.lang.String!>);
+    method @Deprecated public androidx.slice.Slice.Builder addIcon(androidx.core.graphics.drawable.IconCompat, String?, java.lang.String!...);
+    method @Deprecated public androidx.slice.Slice.Builder addIcon(androidx.core.graphics.drawable.IconCompat, String?, java.util.List<java.lang.String!>);
+    method @Deprecated public androidx.slice.Slice.Builder addInt(int, String?, java.lang.String!...);
+    method @Deprecated public androidx.slice.Slice.Builder addInt(int, String?, java.util.List<java.lang.String!>);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public androidx.slice.Slice.Builder addItem(androidx.slice.SliceItem);
+    method @Deprecated public androidx.slice.Slice.Builder addLong(long, String?, java.lang.String!...);
+    method @Deprecated public androidx.slice.Slice.Builder addLong(long, String?, java.util.List<java.lang.String!>);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, String?, java.lang.String!...);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, String?, java.util.List<java.lang.String!>);
+    method @Deprecated public androidx.slice.Slice.Builder addSubSlice(androidx.slice.Slice);
+    method @Deprecated public androidx.slice.Slice.Builder addSubSlice(androidx.slice.Slice, String?);
+    method @Deprecated public androidx.slice.Slice.Builder addText(CharSequence?, String?, java.lang.String!...);
+    method @Deprecated public androidx.slice.Slice.Builder addText(CharSequence?, String?, java.util.List<java.lang.String!>);
     method @Deprecated public androidx.slice.Slice.Builder! addTimestamp(long, String?, java.lang.String!...);
-    method public androidx.slice.Slice.Builder addTimestamp(long, String?, java.util.List<java.lang.String!>);
-    method public androidx.slice.Slice build();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.Slice.Builder setSpec(androidx.slice.SliceSpec?);
+    method @Deprecated public androidx.slice.Slice.Builder addTimestamp(long, String?, java.util.List<java.lang.String!>);
+    method @Deprecated public androidx.slice.Slice build();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.Slice.Builder setSpec(androidx.slice.SliceSpec?);
   }
 
-  @RequiresApi(28) public class SliceConvert {
-    method public static android.app.slice.Slice? unwrap(androidx.slice.Slice?);
-    method public static androidx.slice.Slice? wrap(android.app.slice.Slice?, android.content.Context);
+  @Deprecated @RequiresApi(28) public class SliceConvert {
+    method @Deprecated public static android.app.slice.Slice? unwrap(androidx.slice.Slice?);
+    method @Deprecated public static androidx.slice.Slice? wrap(android.app.slice.Slice?, android.content.Context);
   }
 
-  @RequiresApi(19) @androidx.versionedparcelable.VersionedParcelize(allowSerialization=true, ignoreParcelables=true, isCustom=true) public final class SliceItem extends androidx.versionedparcelable.CustomVersionedParcelable {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem();
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem(android.app.PendingIntent, androidx.slice.Slice?, String, String?, String![]);
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem(androidx.slice.SliceItem.ActionHandler, androidx.slice.Slice?, String, String?, String![]);
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem(Object!, String, String?, String![]);
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem(Object!, String, String?, java.util.List<java.lang.String!>);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void addHint(String);
-    method public static android.text.ParcelableSpan createSensitiveSpan();
-    method public void fireAction(android.content.Context?, android.content.Intent?) throws android.app.PendingIntent.CanceledException;
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean fireActionInternal(android.content.Context?, android.content.Intent?) throws android.app.PendingIntent.CanceledException;
-    method public android.app.PendingIntent? getAction();
-    method public String getFormat();
-    method public java.util.List<java.lang.String!> getHints();
-    method public androidx.core.graphics.drawable.IconCompat? getIcon();
-    method public int getInt();
-    method public long getLong();
-    method public CharSequence? getRedactedText();
-    method @RequiresApi(20) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.app.RemoteInput? getRemoteInput();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public CharSequence? getSanitizedText();
-    method public androidx.slice.Slice? getSlice();
-    method public String? getSubType();
-    method public CharSequence? getText();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public boolean hasAnyHints(java.lang.String!...);
-    method public boolean hasHint(String);
+  @Deprecated @RequiresApi(19) @androidx.versionedparcelable.VersionedParcelize(allowSerialization=true, ignoreParcelables=true, isCustom=true) public final class SliceItem extends androidx.versionedparcelable.CustomVersionedParcelable {
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem();
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem(android.app.PendingIntent, androidx.slice.Slice?, String, String?, String![]);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem(androidx.slice.SliceItem.ActionHandler, androidx.slice.Slice?, String, String?, String![]);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem(Object!, String, String?, String![]);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem(Object!, String, String?, java.util.List<java.lang.String!>);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void addHint(String);
+    method @Deprecated public static android.text.ParcelableSpan createSensitiveSpan();
+    method @Deprecated public void fireAction(android.content.Context?, android.content.Intent?) throws android.app.PendingIntent.CanceledException;
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean fireActionInternal(android.content.Context?, android.content.Intent?) throws android.app.PendingIntent.CanceledException;
+    method @Deprecated public android.app.PendingIntent? getAction();
+    method @Deprecated public String getFormat();
+    method @Deprecated public java.util.List<java.lang.String!> getHints();
+    method @Deprecated public androidx.core.graphics.drawable.IconCompat? getIcon();
+    method @Deprecated public int getInt();
+    method @Deprecated public long getLong();
+    method @Deprecated public CharSequence? getRedactedText();
+    method @Deprecated @RequiresApi(20) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.app.RemoteInput? getRemoteInput();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public CharSequence? getSanitizedText();
+    method @Deprecated public androidx.slice.Slice? getSlice();
+    method @Deprecated public String? getSubType();
+    method @Deprecated public CharSequence? getText();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public boolean hasAnyHints(java.lang.String!...);
+    method @Deprecated public boolean hasHint(String);
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface SliceItem.ActionHandler {
-    method public void onAction(androidx.slice.SliceItem, android.content.Context?, android.content.Intent?);
+  @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface SliceItem.ActionHandler {
+    method @Deprecated public void onAction(androidx.slice.SliceItem, android.content.Context?, android.content.Intent?);
   }
 
-  @RequiresApi(19) public abstract class SliceManager {
-    method @androidx.core.content.PermissionChecker.PermissionResult public abstract int checkSlicePermission(android.net.Uri, int, int);
-    method public static androidx.slice.SliceManager getInstance(android.content.Context);
-    method public abstract java.util.List<android.net.Uri!> getPinnedSlices();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public abstract java.util.Set<androidx.slice.SliceSpec!> getPinnedSpecs(android.net.Uri);
-    method public abstract void grantSlicePermission(String, android.net.Uri);
-    method public abstract void revokeSlicePermission(String, android.net.Uri);
+  @Deprecated @RequiresApi(19) public abstract class SliceManager {
+    method @Deprecated @androidx.core.content.PermissionChecker.PermissionResult public abstract int checkSlicePermission(android.net.Uri, int, int);
+    method @Deprecated public static androidx.slice.SliceManager getInstance(android.content.Context);
+    method @Deprecated public abstract java.util.List<android.net.Uri!> getPinnedSlices();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public abstract java.util.Set<androidx.slice.SliceSpec!> getPinnedSpecs(android.net.Uri);
+    method @Deprecated public abstract void grantSlicePermission(String, android.net.Uri);
+    method @Deprecated public abstract void revokeSlicePermission(String, android.net.Uri);
   }
 
-  public abstract class SliceProvider extends android.content.ContentProvider implements androidx.core.app.CoreComponentFactory.CompatWrapped {
-    ctor public SliceProvider();
-    ctor public SliceProvider(java.lang.String!...);
-    method public final int bulkInsert(android.net.Uri, android.content.ContentValues![]);
-    method @RequiresApi(19) public final android.net.Uri? canonicalize(android.net.Uri);
-    method public final int delete(android.net.Uri, String?, String![]?);
-    method @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static androidx.slice.Clock? getClock();
-    method @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static java.util.Set<androidx.slice.SliceSpec!>? getCurrentSpecs();
-    method @RequiresApi(19) public java.util.List<android.net.Uri!> getPinnedSlices();
-    method public final String? getType(android.net.Uri);
+  @Deprecated public abstract class SliceProvider extends android.content.ContentProvider implements androidx.core.app.CoreComponentFactory.CompatWrapped {
+    ctor @Deprecated public SliceProvider();
+    ctor @Deprecated public SliceProvider(java.lang.String!...);
+    method @Deprecated public final int bulkInsert(android.net.Uri, android.content.ContentValues![]);
+    method @Deprecated @RequiresApi(19) public final android.net.Uri? canonicalize(android.net.Uri);
+    method @Deprecated public final int delete(android.net.Uri, String?, String![]?);
+    method @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static androidx.slice.Clock? getClock();
+    method @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static java.util.Set<androidx.slice.SliceSpec!>? getCurrentSpecs();
+    method @Deprecated @RequiresApi(19) public java.util.List<android.net.Uri!> getPinnedSlices();
+    method @Deprecated public final String? getType(android.net.Uri);
     method @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public Object? getWrapper();
-    method public final android.net.Uri? insert(android.net.Uri, android.content.ContentValues?);
-    method @RequiresApi(19) public abstract androidx.slice.Slice? onBindSlice(android.net.Uri);
-    method public final boolean onCreate();
-    method public android.app.PendingIntent? onCreatePermissionRequest(android.net.Uri, String);
-    method @RequiresApi(19) public abstract boolean onCreateSliceProvider();
-    method @RequiresApi(19) public java.util.Collection<android.net.Uri!> onGetSliceDescendants(android.net.Uri);
-    method @RequiresApi(19) public android.net.Uri onMapIntentToUri(android.content.Intent);
-    method @RequiresApi(19) public void onSlicePinned(android.net.Uri);
-    method @RequiresApi(19) public void onSliceUnpinned(android.net.Uri);
-    method @RequiresApi(28) public final android.database.Cursor? query(android.net.Uri, String![]?, android.os.Bundle?, android.os.CancellationSignal?);
-    method public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?);
-    method @RequiresApi(16) public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?, android.os.CancellationSignal?);
-    method public final int update(android.net.Uri, android.content.ContentValues?, String?, String![]?);
+    method @Deprecated public final android.net.Uri? insert(android.net.Uri, android.content.ContentValues?);
+    method @Deprecated @RequiresApi(19) public abstract androidx.slice.Slice? onBindSlice(android.net.Uri);
+    method @Deprecated public final boolean onCreate();
+    method @Deprecated public android.app.PendingIntent? onCreatePermissionRequest(android.net.Uri, String);
+    method @Deprecated @RequiresApi(19) public abstract boolean onCreateSliceProvider();
+    method @Deprecated @RequiresApi(19) public java.util.Collection<android.net.Uri!> onGetSliceDescendants(android.net.Uri);
+    method @Deprecated @RequiresApi(19) public android.net.Uri onMapIntentToUri(android.content.Intent);
+    method @Deprecated @RequiresApi(19) public void onSlicePinned(android.net.Uri);
+    method @Deprecated @RequiresApi(19) public void onSliceUnpinned(android.net.Uri);
+    method @Deprecated @RequiresApi(28) public final android.database.Cursor? query(android.net.Uri, String![]?, android.os.Bundle?, android.os.CancellationSignal?);
+    method @Deprecated public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?);
+    method @Deprecated @RequiresApi(16) public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?, android.os.CancellationSignal?);
+    method @Deprecated public final int update(android.net.Uri, android.content.ContentValues?, String?, String![]?);
   }
 
-  @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.versionedparcelable.VersionedParcelize(allowSerialization=true) public final class SliceSpec implements androidx.versionedparcelable.VersionedParcelable {
-    ctor public SliceSpec(String, int);
-    method public boolean canRender(androidx.slice.SliceSpec);
-    method public int getRevision();
-    method public String getType();
+  @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.versionedparcelable.VersionedParcelize(allowSerialization=true) public final class SliceSpec implements androidx.versionedparcelable.VersionedParcelable {
+    ctor @Deprecated public SliceSpec(String, int);
+    method @Deprecated public boolean canRender(androidx.slice.SliceSpec);
+    method @Deprecated public int getRevision();
+    method @Deprecated public String getType();
   }
 
-  @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SliceSpecs {
-    field public static final androidx.slice.SliceSpec! BASIC;
-    field public static final androidx.slice.SliceSpec! LIST;
-    field public static final androidx.slice.SliceSpec! LIST_V2;
-    field public static final androidx.slice.SliceSpec! MESSAGING;
+  @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SliceSpecs {
+    field @Deprecated public static final androidx.slice.SliceSpec! BASIC;
+    field @Deprecated public static final androidx.slice.SliceSpec! LIST;
+    field @Deprecated public static final androidx.slice.SliceSpec! LIST_V2;
+    field @Deprecated public static final androidx.slice.SliceSpec! MESSAGING;
   }
 
-  @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SystemClock implements androidx.slice.Clock {
-    ctor public SystemClock();
-    method public long currentTimeMillis();
+  @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SystemClock implements androidx.slice.Clock {
+    ctor @Deprecated public SystemClock();
+    method @Deprecated public long currentTimeMillis();
   }
 
 }
 
 package androidx.slice.compat {
 
-  @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class CompatPermissionManager {
-    ctor public CompatPermissionManager(android.content.Context, String, int, String![]);
-    method public int checkSlicePermission(android.net.Uri, int, int);
-    method public void grantSlicePermission(android.net.Uri, String);
-    method public void revokeSlicePermission(android.net.Uri, String);
-    field public static final String ALL_SUFFIX = "_all";
+  @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class CompatPermissionManager {
+    ctor @Deprecated public CompatPermissionManager(android.content.Context, String, int, String![]);
+    method @Deprecated public int checkSlicePermission(android.net.Uri, int, int);
+    method @Deprecated public void grantSlicePermission(android.net.Uri, String);
+    method @Deprecated public void revokeSlicePermission(android.net.Uri, String);
+    field @Deprecated public static final String ALL_SUFFIX = "_all";
   }
 
-  public static class CompatPermissionManager.PermissionState {
-    method public String getKey();
-    method public boolean hasAccess(java.util.List<java.lang.String!>);
-    method public boolean hasAllPermissions();
-    method public java.util.Set<java.lang.String!> toPersistable();
+  @Deprecated public static class CompatPermissionManager.PermissionState {
+    method @Deprecated public String getKey();
+    method @Deprecated public boolean hasAccess(java.util.List<java.lang.String!>);
+    method @Deprecated public boolean hasAllPermissions();
+    method @Deprecated public java.util.Set<java.lang.String!> toPersistable();
   }
 
-  @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class SliceProviderCompat {
-    ctor public SliceProviderCompat(androidx.slice.SliceProvider, androidx.slice.compat.CompatPermissionManager, android.content.Context);
-    method public static void addSpecs(android.os.Bundle, java.util.Set<androidx.slice.SliceSpec!>);
-    method public static androidx.slice.Slice? bindSlice(android.content.Context, android.content.Intent, java.util.Set<androidx.slice.SliceSpec!>);
-    method public static androidx.slice.Slice? bindSlice(android.content.Context, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>);
-    method public android.os.Bundle? call(String, String?, android.os.Bundle);
-    method public static int checkSlicePermission(android.content.Context, String?, android.net.Uri, int, int);
-    method public String? getCallingPackage();
-    method public static java.util.List<android.net.Uri!> getPinnedSlices(android.content.Context);
-    method public static java.util.Set<androidx.slice.SliceSpec!>? getPinnedSpecs(android.content.Context, android.net.Uri);
-    method public static java.util.Collection<android.net.Uri!> getSliceDescendants(android.content.Context, android.net.Uri);
-    method public static java.util.Set<androidx.slice.SliceSpec!> getSpecs(android.os.Bundle);
-    method public static void grantSlicePermission(android.content.Context, String?, String?, android.net.Uri);
-    method public static android.net.Uri? mapIntentToUri(android.content.Context, android.content.Intent);
-    method public static void pinSlice(android.content.Context, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>);
-    method public static void revokeSlicePermission(android.content.Context, String?, String?, android.net.Uri);
-    method public static void unpinSlice(android.content.Context, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>);
-    field public static final String ARG_SUPPORTS_VERSIONED_PARCELABLE = "supports_versioned_parcelable";
-    field public static final String EXTRA_BIND_URI = "slice_uri";
-    field public static final String EXTRA_INTENT = "slice_intent";
-    field public static final String EXTRA_PID = "pid";
-    field public static final String EXTRA_PKG = "pkg";
-    field public static final String EXTRA_PROVIDER_PKG = "provider_pkg";
-    field public static final String EXTRA_RESULT = "result";
-    field public static final String EXTRA_SLICE = "slice";
-    field public static final String EXTRA_SLICE_DESCENDANTS = "slice_descendants";
-    field public static final String EXTRA_SUPPORTED_SPECS = "specs";
-    field public static final String EXTRA_SUPPORTED_SPECS_REVS = "revs";
-    field public static final String EXTRA_UID = "uid";
-    field public static final String METHOD_CHECK_PERMISSION = "check_perms";
-    field public static final String METHOD_GET_DESCENDANTS = "get_descendants";
-    field public static final String METHOD_GET_PINNED_SPECS = "get_specs";
-    field public static final String METHOD_GRANT_PERMISSION = "grant_perms";
-    field public static final String METHOD_MAP_INTENT = "map_slice";
-    field public static final String METHOD_MAP_ONLY_INTENT = "map_only";
-    field public static final String METHOD_PIN = "pin_slice";
-    field public static final String METHOD_REVOKE_PERMISSION = "revoke_perms";
-    field public static final String METHOD_SLICE = "bind_slice";
-    field public static final String METHOD_UNPIN = "unpin_slice";
-    field public static final String PERMS_PREFIX = "slice_perms_";
+  @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class SliceProviderCompat {
+    ctor @Deprecated public SliceProviderCompat(androidx.slice.SliceProvider, androidx.slice.compat.CompatPermissionManager, android.content.Context);
+    method @Deprecated public static void addSpecs(android.os.Bundle, java.util.Set<androidx.slice.SliceSpec!>);
+    method @Deprecated public static androidx.slice.Slice? bindSlice(android.content.Context, android.content.Intent, java.util.Set<androidx.slice.SliceSpec!>);
+    method @Deprecated public static androidx.slice.Slice? bindSlice(android.content.Context, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>);
+    method @Deprecated public android.os.Bundle? call(String, String?, android.os.Bundle);
+    method @Deprecated public static int checkSlicePermission(android.content.Context, String?, android.net.Uri, int, int);
+    method @Deprecated public String? getCallingPackage();
+    method @Deprecated public static java.util.List<android.net.Uri!> getPinnedSlices(android.content.Context);
+    method @Deprecated public static java.util.Set<androidx.slice.SliceSpec!>? getPinnedSpecs(android.content.Context, android.net.Uri);
+    method @Deprecated public static java.util.Collection<android.net.Uri!> getSliceDescendants(android.content.Context, android.net.Uri);
+    method @Deprecated public static java.util.Set<androidx.slice.SliceSpec!> getSpecs(android.os.Bundle);
+    method @Deprecated public static void grantSlicePermission(android.content.Context, String?, String?, android.net.Uri);
+    method @Deprecated public static android.net.Uri? mapIntentToUri(android.content.Context, android.content.Intent);
+    method @Deprecated public static void pinSlice(android.content.Context, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>);
+    method @Deprecated public static void revokeSlicePermission(android.content.Context, String?, String?, android.net.Uri);
+    method @Deprecated public static void unpinSlice(android.content.Context, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>);
+    field @Deprecated public static final String ARG_SUPPORTS_VERSIONED_PARCELABLE = "supports_versioned_parcelable";
+    field @Deprecated public static final String EXTRA_BIND_URI = "slice_uri";
+    field @Deprecated public static final String EXTRA_INTENT = "slice_intent";
+    field @Deprecated public static final String EXTRA_PID = "pid";
+    field @Deprecated public static final String EXTRA_PKG = "pkg";
+    field @Deprecated public static final String EXTRA_PROVIDER_PKG = "provider_pkg";
+    field @Deprecated public static final String EXTRA_RESULT = "result";
+    field @Deprecated public static final String EXTRA_SLICE = "slice";
+    field @Deprecated public static final String EXTRA_SLICE_DESCENDANTS = "slice_descendants";
+    field @Deprecated public static final String EXTRA_SUPPORTED_SPECS = "specs";
+    field @Deprecated public static final String EXTRA_SUPPORTED_SPECS_REVS = "revs";
+    field @Deprecated public static final String EXTRA_UID = "uid";
+    field @Deprecated public static final String METHOD_CHECK_PERMISSION = "check_perms";
+    field @Deprecated public static final String METHOD_GET_DESCENDANTS = "get_descendants";
+    field @Deprecated public static final String METHOD_GET_PINNED_SPECS = "get_specs";
+    field @Deprecated public static final String METHOD_GRANT_PERMISSION = "grant_perms";
+    field @Deprecated public static final String METHOD_MAP_INTENT = "map_slice";
+    field @Deprecated public static final String METHOD_MAP_ONLY_INTENT = "map_only";
+    field @Deprecated public static final String METHOD_PIN = "pin_slice";
+    field @Deprecated public static final String METHOD_REVOKE_PERMISSION = "revoke_perms";
+    field @Deprecated public static final String METHOD_SLICE = "bind_slice";
+    field @Deprecated public static final String METHOD_UNPIN = "unpin_slice";
+    field @Deprecated public static final String PERMS_PREFIX = "slice_perms_";
   }
 
 }
 
 package androidx.slice.core {
 
-  @RequiresApi(19) public interface SliceAction {
-    method public android.app.PendingIntent getAction();
-    method public CharSequence? getContentDescription();
-    method public androidx.core.graphics.drawable.IconCompat? getIcon();
-    method @androidx.slice.core.SliceHints.ImageMode public int getImageMode();
-    method public String? getKey();
-    method public int getPriority();
-    method public CharSequence getTitle();
-    method public boolean isActivity();
-    method public boolean isChecked();
-    method public boolean isDefaultToggle();
-    method public boolean isToggle();
-    method public androidx.slice.core.SliceAction setChecked(boolean);
-    method public androidx.slice.core.SliceAction setContentDescription(CharSequence);
-    method public androidx.slice.core.SliceAction setKey(String);
-    method public androidx.slice.core.SliceAction setPriority(@IntRange(from=0) int);
+  @Deprecated @RequiresApi(19) public interface SliceAction {
+    method @Deprecated public android.app.PendingIntent getAction();
+    method @Deprecated public CharSequence? getContentDescription();
+    method @Deprecated public androidx.core.graphics.drawable.IconCompat? getIcon();
+    method @Deprecated @androidx.slice.core.SliceHints.ImageMode public int getImageMode();
+    method @Deprecated public String? getKey();
+    method @Deprecated public int getPriority();
+    method @Deprecated public CharSequence getTitle();
+    method @Deprecated public boolean isActivity();
+    method @Deprecated public boolean isChecked();
+    method @Deprecated public boolean isDefaultToggle();
+    method @Deprecated public boolean isToggle();
+    method @Deprecated public androidx.slice.core.SliceAction setChecked(boolean);
+    method @Deprecated public androidx.slice.core.SliceAction setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.core.SliceAction setKey(String);
+    method @Deprecated public androidx.slice.core.SliceAction setPriority(@IntRange(from=0) int);
   }
 
-  @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SliceActionImpl implements androidx.slice.core.SliceAction {
-    ctor public SliceActionImpl(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, @androidx.slice.core.SliceHints.ImageMode int, CharSequence);
-    ctor public SliceActionImpl(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence);
-    ctor public SliceActionImpl(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
-    ctor public SliceActionImpl(android.app.PendingIntent, CharSequence, boolean);
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceActionImpl(androidx.slice.SliceItem);
-    method public androidx.slice.Slice buildPrimaryActionSlice(androidx.slice.Slice.Builder);
-    method public androidx.slice.Slice buildSlice(androidx.slice.Slice.Builder);
-    method public android.app.PendingIntent getAction();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.SliceItem? getActionItem();
-    method public CharSequence? getContentDescription();
-    method public androidx.core.graphics.drawable.IconCompat? getIcon();
-    method @androidx.slice.core.SliceHints.ImageMode public int getImageMode();
-    method public String? getKey();
-    method public int getPriority();
-    method public androidx.slice.SliceItem? getSliceItem();
-    method public String? getSubtype();
-    method public CharSequence getTitle();
-    method public boolean isActivity();
-    method public boolean isChecked();
-    method public boolean isDefaultToggle();
-    method public boolean isToggle();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static int parseImageMode(androidx.slice.SliceItem);
-    method public void setActivity(boolean);
-    method public androidx.slice.core.SliceActionImpl setChecked(boolean);
-    method public androidx.slice.core.SliceAction? setContentDescription(CharSequence);
-    method public androidx.slice.core.SliceActionImpl setKey(String);
-    method public androidx.slice.core.SliceActionImpl setPriority(@IntRange(from=0) int);
+  @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SliceActionImpl implements androidx.slice.core.SliceAction {
+    ctor @Deprecated public SliceActionImpl(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, @androidx.slice.core.SliceHints.ImageMode int, CharSequence);
+    ctor @Deprecated public SliceActionImpl(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence);
+    ctor @Deprecated public SliceActionImpl(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
+    ctor @Deprecated public SliceActionImpl(android.app.PendingIntent, CharSequence, boolean);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceActionImpl(androidx.slice.SliceItem);
+    method @Deprecated public androidx.slice.Slice buildPrimaryActionSlice(androidx.slice.Slice.Builder);
+    method @Deprecated public androidx.slice.Slice buildSlice(androidx.slice.Slice.Builder);
+    method @Deprecated public android.app.PendingIntent getAction();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.SliceItem? getActionItem();
+    method @Deprecated public CharSequence? getContentDescription();
+    method @Deprecated public androidx.core.graphics.drawable.IconCompat? getIcon();
+    method @Deprecated @androidx.slice.core.SliceHints.ImageMode public int getImageMode();
+    method @Deprecated public String? getKey();
+    method @Deprecated public int getPriority();
+    method @Deprecated public androidx.slice.SliceItem? getSliceItem();
+    method @Deprecated public String? getSubtype();
+    method @Deprecated public CharSequence getTitle();
+    method @Deprecated public boolean isActivity();
+    method @Deprecated public boolean isChecked();
+    method @Deprecated public boolean isDefaultToggle();
+    method @Deprecated public boolean isToggle();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static int parseImageMode(androidx.slice.SliceItem);
+    method @Deprecated public void setActivity(boolean);
+    method @Deprecated public androidx.slice.core.SliceActionImpl setChecked(boolean);
+    method @Deprecated public androidx.slice.core.SliceAction? setContentDescription(CharSequence);
+    method @Deprecated public androidx.slice.core.SliceActionImpl setKey(String);
+    method @Deprecated public androidx.slice.core.SliceActionImpl setPriority(@IntRange(from=0) int);
   }
 
-  @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SliceHints {
-    field public static final int ACTION_WITH_LABEL = 6; // 0x6
-    field public static final int DETERMINATE_RANGE = 0; // 0x0
-    field public static final String HINT_ACTIVITY = "activity";
-    field public static final String HINT_CACHED = "cached";
-    field public static final String HINT_END_OF_SECTION = "end_of_section";
-    field public static final String HINT_OVERLAY = "overlay";
-    field public static final String HINT_RAW = "raw";
-    field public static final String HINT_SELECTION_OPTION = "selection_option";
-    field public static final String HINT_SHOW_LABEL = "show_label";
-    field public static final int ICON_IMAGE = 0; // 0x0
-    field public static final int INDETERMINATE_RANGE = 1; // 0x1
-    field public static final long INFINITY = -1L; // 0xffffffffffffffffL
-    field public static final int LARGE_IMAGE = 2; // 0x2
-    field public static final int RAW_IMAGE_LARGE = 4; // 0x4
-    field public static final int RAW_IMAGE_SMALL = 3; // 0x3
-    field public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
-    field public static final int SMALL_IMAGE = 1; // 0x1
-    field public static final int STAR_RATING = 2; // 0x2
-    field public static final String SUBTYPE_ACTION_KEY = "action_key";
-    field public static final String SUBTYPE_DATE_PICKER = "date_picker";
-    field public static final String SUBTYPE_HOST_EXTRAS = "host_extras";
-    field public static final String SUBTYPE_MILLIS = "millis";
-    field public static final String SUBTYPE_MIN = "min";
-    field public static final String SUBTYPE_SELECTION = "selection";
-    field public static final String SUBTYPE_SELECTION_OPTION_KEY = "selection_option_key";
-    field public static final String SUBTYPE_SELECTION_OPTION_VALUE = "selection_option_value";
-    field public static final String SUBTYPE_TIME_PICKER = "time_picker";
-    field public static final int UNKNOWN_IMAGE = 5; // 0x5
+  @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SliceHints {
+    field @Deprecated public static final int ACTION_WITH_LABEL = 6; // 0x6
+    field @Deprecated public static final int DETERMINATE_RANGE = 0; // 0x0
+    field @Deprecated public static final String HINT_ACTIVITY = "activity";
+    field @Deprecated public static final String HINT_CACHED = "cached";
+    field @Deprecated public static final String HINT_END_OF_SECTION = "end_of_section";
+    field @Deprecated public static final String HINT_OVERLAY = "overlay";
+    field @Deprecated public static final String HINT_RAW = "raw";
+    field @Deprecated public static final String HINT_SELECTION_OPTION = "selection_option";
+    field @Deprecated public static final String HINT_SHOW_LABEL = "show_label";
+    field @Deprecated public static final int ICON_IMAGE = 0; // 0x0
+    field @Deprecated public static final int INDETERMINATE_RANGE = 1; // 0x1
+    field @Deprecated public static final long INFINITY = -1L; // 0xffffffffffffffffL
+    field @Deprecated public static final int LARGE_IMAGE = 2; // 0x2
+    field @Deprecated public static final int RAW_IMAGE_LARGE = 4; // 0x4
+    field @Deprecated public static final int RAW_IMAGE_SMALL = 3; // 0x3
+    field @Deprecated public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
+    field @Deprecated public static final int SMALL_IMAGE = 1; // 0x1
+    field @Deprecated public static final int STAR_RATING = 2; // 0x2
+    field @Deprecated public static final String SUBTYPE_ACTION_KEY = "action_key";
+    field @Deprecated public static final String SUBTYPE_DATE_PICKER = "date_picker";
+    field @Deprecated public static final String SUBTYPE_HOST_EXTRAS = "host_extras";
+    field @Deprecated public static final String SUBTYPE_MILLIS = "millis";
+    field @Deprecated public static final String SUBTYPE_MIN = "min";
+    field @Deprecated public static final String SUBTYPE_SELECTION = "selection";
+    field @Deprecated public static final String SUBTYPE_SELECTION_OPTION_KEY = "selection_option_key";
+    field @Deprecated public static final String SUBTYPE_SELECTION_OPTION_VALUE = "selection_option_value";
+    field @Deprecated public static final String SUBTYPE_TIME_PICKER = "time_picker";
+    field @Deprecated public static final int UNKNOWN_IMAGE = 5; // 0x5
   }
 
-  @IntDef({androidx.slice.core.SliceHints.LARGE_IMAGE, androidx.slice.core.SliceHints.SMALL_IMAGE, androidx.slice.core.SliceHints.ICON_IMAGE, androidx.slice.core.SliceHints.RAW_IMAGE_SMALL, androidx.slice.core.SliceHints.RAW_IMAGE_LARGE, androidx.slice.core.SliceHints.UNKNOWN_IMAGE, androidx.slice.core.SliceHints.ACTION_WITH_LABEL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SliceHints.ImageMode {
+  @Deprecated @IntDef({androidx.slice.core.SliceHints.LARGE_IMAGE, androidx.slice.core.SliceHints.SMALL_IMAGE, androidx.slice.core.SliceHints.ICON_IMAGE, androidx.slice.core.SliceHints.RAW_IMAGE_SMALL, androidx.slice.core.SliceHints.RAW_IMAGE_LARGE, androidx.slice.core.SliceHints.UNKNOWN_IMAGE, androidx.slice.core.SliceHints.ACTION_WITH_LABEL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SliceHints.ImageMode {
   }
 
-  @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SliceQuery {
-    method public static androidx.slice.SliceItem? find(androidx.slice.Slice?, String?);
-    method public static androidx.slice.SliceItem? find(androidx.slice.Slice?, String?, String?, String?);
-    method public static androidx.slice.SliceItem? find(androidx.slice.Slice?, String?, String![]?, String![]?);
-    method public static androidx.slice.SliceItem? find(androidx.slice.SliceItem?, String?);
-    method public static androidx.slice.SliceItem? find(androidx.slice.SliceItem?, String?, String?, String?);
-    method public static androidx.slice.SliceItem? find(androidx.slice.SliceItem?, String?, String![]?, String![]?);
-    method public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.Slice, String?, String?, String?);
-    method public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.Slice, String?, String![]?, String![]?);
-    method public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.SliceItem, String?);
-    method public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.SliceItem, String?, String?, String?);
-    method public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.SliceItem, String?, String![]?, String![]?);
-    method public static androidx.slice.SliceItem? findItem(androidx.slice.Slice, android.net.Uri);
-    method public static androidx.slice.SliceItem? findNotContaining(androidx.slice.SliceItem?, java.util.List<androidx.slice.SliceItem!>);
-    method public static androidx.slice.SliceItem? findSubtype(androidx.slice.Slice?, String?, String?);
-    method public static androidx.slice.SliceItem? findSubtype(androidx.slice.SliceItem?, String?, String?);
-    method public static androidx.slice.SliceItem? findTopLevelItem(androidx.slice.Slice, String?, String?, String![]?, String![]?);
-    method public static boolean hasAnyHints(androidx.slice.SliceItem, java.lang.String!...);
-    method public static boolean hasHints(androidx.slice.Slice, java.lang.String!...);
-    method public static boolean hasHints(androidx.slice.SliceItem, java.lang.String!...);
+  @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SliceQuery {
+    method @Deprecated public static androidx.slice.SliceItem? find(androidx.slice.Slice?, String?);
+    method @Deprecated public static androidx.slice.SliceItem? find(androidx.slice.Slice?, String?, String?, String?);
+    method @Deprecated public static androidx.slice.SliceItem? find(androidx.slice.Slice?, String?, String![]?, String![]?);
+    method @Deprecated public static androidx.slice.SliceItem? find(androidx.slice.SliceItem?, String?);
+    method @Deprecated public static androidx.slice.SliceItem? find(androidx.slice.SliceItem?, String?, String?, String?);
+    method @Deprecated public static androidx.slice.SliceItem? find(androidx.slice.SliceItem?, String?, String![]?, String![]?);
+    method @Deprecated public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.Slice, String?, String?, String?);
+    method @Deprecated public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.Slice, String?, String![]?, String![]?);
+    method @Deprecated public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.SliceItem, String?);
+    method @Deprecated public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.SliceItem, String?, String?, String?);
+    method @Deprecated public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.SliceItem, String?, String![]?, String![]?);
+    method @Deprecated public static androidx.slice.SliceItem? findItem(androidx.slice.Slice, android.net.Uri);
+    method @Deprecated public static androidx.slice.SliceItem? findNotContaining(androidx.slice.SliceItem?, java.util.List<androidx.slice.SliceItem!>);
+    method @Deprecated public static androidx.slice.SliceItem? findSubtype(androidx.slice.Slice?, String?, String?);
+    method @Deprecated public static androidx.slice.SliceItem? findSubtype(androidx.slice.SliceItem?, String?, String?);
+    method @Deprecated public static androidx.slice.SliceItem? findTopLevelItem(androidx.slice.Slice, String?, String?, String![]?, String![]?);
+    method @Deprecated public static boolean hasAnyHints(androidx.slice.SliceItem, java.lang.String!...);
+    method @Deprecated public static boolean hasHints(androidx.slice.Slice, java.lang.String!...);
+    method @Deprecated public static boolean hasHints(androidx.slice.SliceItem, java.lang.String!...);
   }
 
 }
diff --git a/slice/slice-core/src/main/java/androidx/slice/ArrayUtils.java b/slice/slice-core/src/main/java/androidx/slice/ArrayUtils.java
index e5dd970..82d9b0a 100644
--- a/slice/slice-core/src/main/java/androidx/slice/ArrayUtils.java
+++ b/slice/slice-core/src/main/java/androidx/slice/ArrayUtils.java
@@ -27,6 +27,7 @@
  */
 @RestrictTo(Scope.LIBRARY_GROUP)
 @RequiresApi(19)
+@Deprecated
 class ArrayUtils {
 
     public static <T> boolean contains(T[] array, T item) {
diff --git a/slice/slice-core/src/main/java/androidx/slice/Clock.java b/slice/slice-core/src/main/java/androidx/slice/Clock.java
index f68560c..fc5c059 100644
--- a/slice/slice-core/src/main/java/androidx/slice/Clock.java
+++ b/slice/slice-core/src/main/java/androidx/slice/Clock.java
@@ -25,6 +25,7 @@
  */
 @RestrictTo(LIBRARY_GROUP_PREFIX)
 @RequiresApi(19)
+@Deprecated
 public interface Clock {
     long currentTimeMillis();
 }
diff --git a/slice/slice-core/src/main/java/androidx/slice/CornerDrawable.java b/slice/slice-core/src/main/java/androidx/slice/CornerDrawable.java
index 9ccb915..740cb1c 100644
--- a/slice/slice-core/src/main/java/androidx/slice/CornerDrawable.java
+++ b/slice/slice-core/src/main/java/androidx/slice/CornerDrawable.java
@@ -32,6 +32,7 @@
  *
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@Deprecated
 public class CornerDrawable extends InsetDrawable {
     private float mCornerRadius;
     private final Path mPath = new Path();
diff --git a/slice/slice-core/src/main/java/androidx/slice/Slice.java b/slice/slice-core/src/main/java/androidx/slice/Slice.java
index 8044c1e..b9ce51d 100644
--- a/slice/slice-core/src/main/java/androidx/slice/Slice.java
+++ b/slice/slice-core/src/main/java/androidx/slice/Slice.java
@@ -90,9 +90,14 @@
  * <p>Slices are constructed using {@link androidx.slice.builders.TemplateSliceBuilder}s
  * in a tree structure that provides the OS some information about how the content should be
  * displayed.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @VersionedParcelize(allowSerialization = true, isCustom = true)
 @RequiresApi(19)
+@Deprecated
 public final class Slice extends CustomVersionedParcelable implements VersionedParcelable {
 
     /**
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceConvert.java b/slice/slice-core/src/main/java/androidx/slice/SliceConvert.java
index 3711e65..abae809e 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceConvert.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceConvert.java
@@ -43,8 +43,13 @@
 /**
  * Convert between {@link androidx.slice.Slice androidx.slice.Slice} and
  * {@link android.app.slice.Slice android.app.slice.Slice}
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(28)
+@Deprecated
 public class SliceConvert {
 
     private static final String TAG = "SliceConvert";
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceItem.java b/slice/slice-core/src/main/java/androidx/slice/SliceItem.java
index c35fdef..47d353c 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceItem.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceItem.java
@@ -86,9 +86,14 @@
  * The hints that a {@link SliceItem} are a set of strings which annotate
  * the content. The hints that are guaranteed to be understood by the system
  * are defined on {@link Slice}.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @VersionedParcelize(allowSerialization = true, ignoreParcelables = true, isCustom = true)
 @RequiresApi(19)
+@Deprecated
 public final class SliceItem extends CustomVersionedParcelable {
 
     private static final String HINTS = "hints";
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceItemHolder.java b/slice/slice-core/src/main/java/androidx/slice/SliceItemHolder.java
index ab2ebba..4e822a3 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceItemHolder.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceItemHolder.java
@@ -50,6 +50,7 @@
 @VersionedParcelize(allowSerialization = true, ignoreParcelables = true,
         factory = SliceItemHolder.SliceItemPool.class)
 @RequiresApi(19)
+@Deprecated
 public class SliceItemHolder implements VersionedParcelable {
 
     public static final Object sSerializeLock = new Object();
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceManager.java b/slice/slice-core/src/main/java/androidx/slice/SliceManager.java
index 390f09a..eff8508 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceManager.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceManager.java
@@ -33,8 +33,13 @@
  * Class to handle interactions with {@link Slice}s.
  * <p>
  * The SliceViewManager manages permissions and pinned state for slices.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public abstract class SliceManager {
 
     /**
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceManagerCompat.java b/slice/slice-core/src/main/java/androidx/slice/SliceManagerCompat.java
index 549d939..89b8ba8 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceManagerCompat.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceManagerCompat.java
@@ -32,6 +32,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
+@Deprecated
 class SliceManagerCompat extends SliceManager {
 
     private final Context mContext;
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceManagerWrapper.java b/slice/slice-core/src/main/java/androidx/slice/SliceManagerWrapper.java
index d1bd08e..7d86844 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceManagerWrapper.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceManagerWrapper.java
@@ -36,6 +36,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(api = 28)
+@Deprecated
 class SliceManagerWrapper extends SliceManager {
 
     private final android.app.slice.SliceManager mManager;
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceProvider.java b/slice/slice-core/src/main/java/androidx/slice/SliceProvider.java
index 9cd8ddc..399ba9f 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceProvider.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceProvider.java
@@ -130,7 +130,12 @@
  * </pre>
  *
  * @see Slice
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
+@Deprecated
 public abstract class SliceProvider extends ContentProvider implements
         CoreComponentFactory.CompatWrapped {
 
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceSpec.java b/slice/slice-core/src/main/java/androidx/slice/SliceSpec.java
index 13bacd9..5c7b61f 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceSpec.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceSpec.java
@@ -45,6 +45,7 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
 @VersionedParcelize(allowSerialization = true)
 @RequiresApi(19)
+@Deprecated
 public final class SliceSpec implements VersionedParcelable {
 
     @ParcelField(1)
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceSpecs.java b/slice/slice-core/src/main/java/androidx/slice/SliceSpecs.java
index 436d24b..e07a46f 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceSpecs.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceSpecs.java
@@ -24,6 +24,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
 @RequiresApi(19)
+@Deprecated
 public class SliceSpecs {
 
     /**
diff --git a/slice/slice-core/src/main/java/androidx/slice/SystemClock.java b/slice/slice-core/src/main/java/androidx/slice/SystemClock.java
index 888d3e4..c3f5ac3 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SystemClock.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SystemClock.java
@@ -25,6 +25,7 @@
  */
 @RestrictTo(LIBRARY_GROUP_PREFIX)
 @RequiresApi(19)
+@Deprecated
 public class SystemClock implements Clock {
     @Override
     public long currentTimeMillis() {
diff --git a/slice/slice-core/src/main/java/androidx/slice/compat/CompatPermissionManager.java b/slice/slice-core/src/main/java/androidx/slice/compat/CompatPermissionManager.java
index dc700dd..b5ee3db 100644
--- a/slice/slice-core/src/main/java/androidx/slice/compat/CompatPermissionManager.java
+++ b/slice/slice-core/src/main/java/androidx/slice/compat/CompatPermissionManager.java
@@ -40,6 +40,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @RequiresApi(19)
+@Deprecated
 public class CompatPermissionManager {
     public static final String ALL_SUFFIX = "_all";
 
diff --git a/slice/slice-core/src/main/java/androidx/slice/compat/CompatPinnedList.java b/slice/slice-core/src/main/java/androidx/slice/compat/CompatPinnedList.java
index f9de140..67dea43 100644
--- a/slice/slice-core/src/main/java/androidx/slice/compat/CompatPinnedList.java
+++ b/slice/slice-core/src/main/java/androidx/slice/compat/CompatPinnedList.java
@@ -41,6 +41,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class CompatPinnedList {
 
     private static final String LAST_BOOT = "last_boot";
diff --git a/slice/slice-core/src/main/java/androidx/slice/compat/SlicePermissionActivity.java b/slice/slice-core/src/main/java/androidx/slice/compat/SlicePermissionActivity.java
index 9d704cc..87711b7 100644
--- a/slice/slice-core/src/main/java/androidx/slice/compat/SlicePermissionActivity.java
+++ b/slice/slice-core/src/main/java/androidx/slice/compat/SlicePermissionActivity.java
@@ -43,6 +43,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class SlicePermissionActivity extends AppCompatActivity implements OnClickListener,
         OnDismissListener {
 
diff --git a/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderCompat.java b/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
index a03e977..24a73f3 100644
--- a/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
+++ b/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
@@ -67,6 +67,7 @@
  */
 @RestrictTo(Scope.LIBRARY_GROUP)
 @RequiresApi(19)
+@Deprecated
 public class SliceProviderCompat {
     public static final String PERMS_PREFIX = "slice_perms_";
     private static final String TAG = "SliceProviderCompat";
diff --git a/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java b/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java
index b24671f..69207a4 100644
--- a/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java
+++ b/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java
@@ -43,6 +43,7 @@
 /**
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
+@Deprecated
 public class SliceProviderWrapperContainer {
 
     /**
diff --git a/slice/slice-core/src/main/java/androidx/slice/core/SliceAction.java b/slice/slice-core/src/main/java/androidx/slice/core/SliceAction.java
index 6c47667..0df5949 100644
--- a/slice/slice-core/src/main/java/androidx/slice/core/SliceAction.java
+++ b/slice/slice-core/src/main/java/androidx/slice/core/SliceAction.java
@@ -26,8 +26,13 @@
 
 /**
  * Interface for a slice action, supports tappable icons, custom toggle icons, and default toggles.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public interface SliceAction {
 
     /**
diff --git a/slice/slice-core/src/main/java/androidx/slice/core/SliceActionImpl.java b/slice/slice-core/src/main/java/androidx/slice/core/SliceActionImpl.java
index 503cdbf..cd69d0e 100644
--- a/slice/slice-core/src/main/java/androidx/slice/core/SliceActionImpl.java
+++ b/slice/slice-core/src/main/java/androidx/slice/core/SliceActionImpl.java
@@ -64,6 +64,7 @@
  */
 @RestrictTo(LIBRARY_GROUP_PREFIX)
 @RequiresApi(19)
+@Deprecated
 public class SliceActionImpl implements SliceAction {
 
     // Either mAction or mActionItem must be non-null.
diff --git a/slice/slice-core/src/main/java/androidx/slice/core/SliceHints.java b/slice/slice-core/src/main/java/androidx/slice/core/SliceHints.java
index e08441b..29e0353 100644
--- a/slice/slice-core/src/main/java/androidx/slice/core/SliceHints.java
+++ b/slice/slice-core/src/main/java/androidx/slice/core/SliceHints.java
@@ -31,6 +31,7 @@
  */
 @RestrictTo(LIBRARY_GROUP_PREFIX)
 @RequiresApi(19)
+@Deprecated
 public class SliceHints {
 
     /**
diff --git a/slice/slice-core/src/main/java/androidx/slice/core/SliceQuery.java b/slice/slice-core/src/main/java/androidx/slice/core/SliceQuery.java
index 3111e1b..f4d3121 100644
--- a/slice/slice-core/src/main/java/androidx/slice/core/SliceQuery.java
+++ b/slice/slice-core/src/main/java/androidx/slice/core/SliceQuery.java
@@ -40,6 +40,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
 @RequiresApi(19)
+@Deprecated
 public class SliceQuery {
 
     /**
diff --git a/slice/slice-view/api/current.txt b/slice/slice-view/api/current.txt
index 91918d9..0b172e8 100644
--- a/slice/slice-view/api/current.txt
+++ b/slice/slice-view/api/current.txt
@@ -1,235 +1,235 @@
 // Signature format: 4.0
 package androidx.slice {
 
-  @RequiresApi(19) public class SliceMetadata {
-    method public static androidx.slice.SliceMetadata from(android.content.Context?, androidx.slice.Slice);
-    method public long getExpiry();
-    method public int getHeaderType();
-    method public android.os.Bundle getHostExtras();
-    method public android.app.PendingIntent? getInputRangeAction();
-    method public long getLastUpdatedTime();
-    method public int getLoadingState();
-    method public androidx.slice.core.SliceAction? getPrimaryAction();
-    method public androidx.core.util.Pair<java.lang.Integer!,java.lang.Integer!>? getRange();
-    method public int getRangeValue();
-    method public java.util.List<androidx.slice.core.SliceAction!>? getSliceActions();
-    method public java.util.List<java.lang.String!>? getSliceKeywords();
-    method public CharSequence? getSubtitle();
-    method public CharSequence? getSummary();
-    method public CharSequence? getTitle();
-    method public java.util.List<androidx.slice.core.SliceAction!>! getToggles();
-    method public boolean hasLargeMode();
-    method public boolean isCachedSlice();
-    method public boolean isErrorSlice();
-    method public boolean isPermissionSlice();
-    method public boolean isSelection();
-    method public boolean sendInputRangeAction(int) throws android.app.PendingIntent.CanceledException;
-    method public boolean sendToggleAction(androidx.slice.core.SliceAction!, boolean) throws android.app.PendingIntent.CanceledException;
-    field public static final int LOADED_ALL = 2; // 0x2
-    field public static final int LOADED_NONE = 0; // 0x0
-    field public static final int LOADED_PARTIAL = 1; // 0x1
+  @Deprecated @RequiresApi(19) public class SliceMetadata {
+    method @Deprecated public static androidx.slice.SliceMetadata from(android.content.Context?, androidx.slice.Slice);
+    method @Deprecated public long getExpiry();
+    method @Deprecated public int getHeaderType();
+    method @Deprecated public android.os.Bundle getHostExtras();
+    method @Deprecated public android.app.PendingIntent? getInputRangeAction();
+    method @Deprecated public long getLastUpdatedTime();
+    method @Deprecated public int getLoadingState();
+    method @Deprecated public androidx.slice.core.SliceAction? getPrimaryAction();
+    method @Deprecated public androidx.core.util.Pair<java.lang.Integer!,java.lang.Integer!>? getRange();
+    method @Deprecated public int getRangeValue();
+    method @Deprecated public java.util.List<androidx.slice.core.SliceAction!>? getSliceActions();
+    method @Deprecated public java.util.List<java.lang.String!>? getSliceKeywords();
+    method @Deprecated public CharSequence? getSubtitle();
+    method @Deprecated public CharSequence? getSummary();
+    method @Deprecated public CharSequence? getTitle();
+    method @Deprecated public java.util.List<androidx.slice.core.SliceAction!>! getToggles();
+    method @Deprecated public boolean hasLargeMode();
+    method @Deprecated public boolean isCachedSlice();
+    method @Deprecated public boolean isErrorSlice();
+    method @Deprecated public boolean isPermissionSlice();
+    method @Deprecated public boolean isSelection();
+    method @Deprecated public boolean sendInputRangeAction(int) throws android.app.PendingIntent.CanceledException;
+    method @Deprecated public boolean sendToggleAction(androidx.slice.core.SliceAction!, boolean) throws android.app.PendingIntent.CanceledException;
+    field @Deprecated public static final int LOADED_ALL = 2; // 0x2
+    field @Deprecated public static final int LOADED_NONE = 0; // 0x0
+    field @Deprecated public static final int LOADED_PARTIAL = 1; // 0x1
   }
 
-  @RequiresApi(19) public class SliceStructure {
-    ctor public SliceStructure(androidx.slice.Slice!);
+  @Deprecated @RequiresApi(19) public class SliceStructure {
+    ctor @Deprecated public SliceStructure(androidx.slice.Slice!);
   }
 
-  @RequiresApi(19) public class SliceUtils {
-    method public static androidx.slice.Slice parseSlice(android.content.Context, java.io.InputStream, String, androidx.slice.SliceUtils.SliceActionListener) throws java.io.IOException, androidx.slice.SliceUtils.SliceParseException;
-    method public static void serializeSlice(androidx.slice.Slice, android.content.Context, java.io.OutputStream, androidx.slice.SliceUtils.SerializeOptions) throws java.lang.IllegalArgumentException;
-    method public static androidx.slice.Slice stripSlice(androidx.slice.Slice, int, boolean);
+  @Deprecated @RequiresApi(19) public class SliceUtils {
+    method @Deprecated public static androidx.slice.Slice parseSlice(android.content.Context, java.io.InputStream, String, androidx.slice.SliceUtils.SliceActionListener) throws java.io.IOException, androidx.slice.SliceUtils.SliceParseException;
+    method @Deprecated public static void serializeSlice(androidx.slice.Slice, android.content.Context, java.io.OutputStream, androidx.slice.SliceUtils.SerializeOptions) throws java.lang.IllegalArgumentException;
+    method @Deprecated public static androidx.slice.Slice stripSlice(androidx.slice.Slice, int, boolean);
   }
 
-  public static class SliceUtils.SerializeOptions {
-    ctor public SliceUtils.SerializeOptions();
-    method public androidx.slice.SliceUtils.SerializeOptions! setActionMode(int);
-    method public androidx.slice.SliceUtils.SerializeOptions! setImageConversionFormat(android.graphics.Bitmap.CompressFormat!, int);
-    method public androidx.slice.SliceUtils.SerializeOptions! setImageMode(int);
-    method public androidx.slice.SliceUtils.SerializeOptions! setMaxImageHeight(int);
-    method public androidx.slice.SliceUtils.SerializeOptions! setMaxImageWidth(int);
-    field public static final int MODE_CONVERT = 2; // 0x2
-    field public static final int MODE_REMOVE = 1; // 0x1
-    field public static final int MODE_THROW = 0; // 0x0
+  @Deprecated public static class SliceUtils.SerializeOptions {
+    ctor @Deprecated public SliceUtils.SerializeOptions();
+    method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setActionMode(int);
+    method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setImageConversionFormat(android.graphics.Bitmap.CompressFormat!, int);
+    method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setImageMode(int);
+    method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setMaxImageHeight(int);
+    method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setMaxImageWidth(int);
+    field @Deprecated public static final int MODE_CONVERT = 2; // 0x2
+    field @Deprecated public static final int MODE_REMOVE = 1; // 0x1
+    field @Deprecated public static final int MODE_THROW = 0; // 0x0
   }
 
-  public static interface SliceUtils.SliceActionListener {
-    method public void onSliceAction(android.net.Uri!, android.content.Context!, android.content.Intent!);
+  @Deprecated public static interface SliceUtils.SliceActionListener {
+    method @Deprecated public void onSliceAction(android.net.Uri!, android.content.Context!, android.content.Intent!);
   }
 
-  public static class SliceUtils.SliceParseException extends java.lang.Exception {
+  @Deprecated public static class SliceUtils.SliceParseException extends java.lang.Exception {
   }
 
-  @RequiresApi(19) public abstract class SliceViewManager {
-    method public abstract androidx.slice.Slice? bindSlice(android.content.Intent);
-    method public abstract androidx.slice.Slice? bindSlice(android.net.Uri);
-    method public static androidx.slice.SliceViewManager getInstance(android.content.Context);
-    method @WorkerThread public abstract java.util.Collection<android.net.Uri!> getSliceDescendants(android.net.Uri);
-    method public abstract android.net.Uri? mapIntentToUri(android.content.Intent);
-    method public abstract void pinSlice(android.net.Uri);
-    method public abstract void registerSliceCallback(android.net.Uri, androidx.slice.SliceViewManager.SliceCallback);
-    method public abstract void registerSliceCallback(android.net.Uri, java.util.concurrent.Executor, androidx.slice.SliceViewManager.SliceCallback);
-    method public abstract void unpinSlice(android.net.Uri);
-    method public abstract void unregisterSliceCallback(android.net.Uri, androidx.slice.SliceViewManager.SliceCallback);
+  @Deprecated @RequiresApi(19) public abstract class SliceViewManager {
+    method @Deprecated public abstract androidx.slice.Slice? bindSlice(android.content.Intent);
+    method @Deprecated public abstract androidx.slice.Slice? bindSlice(android.net.Uri);
+    method @Deprecated public static androidx.slice.SliceViewManager getInstance(android.content.Context);
+    method @Deprecated @WorkerThread public abstract java.util.Collection<android.net.Uri!> getSliceDescendants(android.net.Uri);
+    method @Deprecated public abstract android.net.Uri? mapIntentToUri(android.content.Intent);
+    method @Deprecated public abstract void pinSlice(android.net.Uri);
+    method @Deprecated public abstract void registerSliceCallback(android.net.Uri, androidx.slice.SliceViewManager.SliceCallback);
+    method @Deprecated public abstract void registerSliceCallback(android.net.Uri, java.util.concurrent.Executor, androidx.slice.SliceViewManager.SliceCallback);
+    method @Deprecated public abstract void unpinSlice(android.net.Uri);
+    method @Deprecated public abstract void unregisterSliceCallback(android.net.Uri, androidx.slice.SliceViewManager.SliceCallback);
   }
 
-  public static interface SliceViewManager.SliceCallback {
-    method public void onSliceUpdated(androidx.slice.Slice?);
+  @Deprecated public static interface SliceViewManager.SliceCallback {
+    method @Deprecated public void onSliceUpdated(androidx.slice.Slice?);
   }
 
 }
 
 package androidx.slice.widget {
 
-  @RequiresApi(19) public class EventInfo {
-    ctor public EventInfo(int, int, int, int);
-    method public void setPosition(int, int, int);
-    field public static final int ACTION_TYPE_BUTTON = 1; // 0x1
-    field public static final int ACTION_TYPE_CONTENT = 3; // 0x3
-    field public static final int ACTION_TYPE_SEE_MORE = 4; // 0x4
-    field public static final int ACTION_TYPE_SELECTION = 5; // 0x5
-    field public static final int ACTION_TYPE_SLIDER = 2; // 0x2
-    field public static final int ACTION_TYPE_TOGGLE = 0; // 0x0
-    field public static final int POSITION_CELL = 2; // 0x2
-    field public static final int POSITION_END = 1; // 0x1
-    field public static final int POSITION_START = 0; // 0x0
-    field public static final int ROW_TYPE_GRID = 1; // 0x1
-    field public static final int ROW_TYPE_LIST = 0; // 0x0
-    field public static final int ROW_TYPE_MESSAGING = 2; // 0x2
-    field public static final int ROW_TYPE_PROGRESS = 5; // 0x5
-    field public static final int ROW_TYPE_SELECTION = 6; // 0x6
-    field public static final int ROW_TYPE_SHORTCUT = -1; // 0xffffffff
-    field public static final int ROW_TYPE_SLIDER = 4; // 0x4
-    field public static final int ROW_TYPE_TOGGLE = 3; // 0x3
-    field public static final int STATE_OFF = 0; // 0x0
-    field public static final int STATE_ON = 1; // 0x1
-    field public int actionCount;
-    field public int actionIndex;
-    field public int actionPosition;
-    field public int actionType;
-    field public int rowIndex;
-    field public int rowTemplateType;
-    field public int sliceMode;
-    field public int state;
+  @Deprecated @RequiresApi(19) public class EventInfo {
+    ctor @Deprecated public EventInfo(int, int, int, int);
+    method @Deprecated public void setPosition(int, int, int);
+    field @Deprecated public static final int ACTION_TYPE_BUTTON = 1; // 0x1
+    field @Deprecated public static final int ACTION_TYPE_CONTENT = 3; // 0x3
+    field @Deprecated public static final int ACTION_TYPE_SEE_MORE = 4; // 0x4
+    field @Deprecated public static final int ACTION_TYPE_SELECTION = 5; // 0x5
+    field @Deprecated public static final int ACTION_TYPE_SLIDER = 2; // 0x2
+    field @Deprecated public static final int ACTION_TYPE_TOGGLE = 0; // 0x0
+    field @Deprecated public static final int POSITION_CELL = 2; // 0x2
+    field @Deprecated public static final int POSITION_END = 1; // 0x1
+    field @Deprecated public static final int POSITION_START = 0; // 0x0
+    field @Deprecated public static final int ROW_TYPE_GRID = 1; // 0x1
+    field @Deprecated public static final int ROW_TYPE_LIST = 0; // 0x0
+    field @Deprecated public static final int ROW_TYPE_MESSAGING = 2; // 0x2
+    field @Deprecated public static final int ROW_TYPE_PROGRESS = 5; // 0x5
+    field @Deprecated public static final int ROW_TYPE_SELECTION = 6; // 0x6
+    field @Deprecated public static final int ROW_TYPE_SHORTCUT = -1; // 0xffffffff
+    field @Deprecated public static final int ROW_TYPE_SLIDER = 4; // 0x4
+    field @Deprecated public static final int ROW_TYPE_TOGGLE = 3; // 0x3
+    field @Deprecated public static final int STATE_OFF = 0; // 0x0
+    field @Deprecated public static final int STATE_ON = 1; // 0x1
+    field @Deprecated public int actionCount;
+    field @Deprecated public int actionIndex;
+    field @Deprecated public int actionPosition;
+    field @Deprecated public int actionType;
+    field @Deprecated public int rowIndex;
+    field @Deprecated public int rowTemplateType;
+    field @Deprecated public int sliceMode;
+    field @Deprecated public int state;
   }
 
-  @RequiresApi(19) public class GridContent extends androidx.slice.widget.SliceContent {
-    method public android.graphics.Point getFirstImageSize(android.content.Context);
-    method public boolean isValid();
+  @Deprecated @RequiresApi(19) public class GridContent extends androidx.slice.widget.SliceContent {
+    method @Deprecated public android.graphics.Point getFirstImageSize(android.content.Context);
+    method @Deprecated public boolean isValid();
   }
 
-  @RequiresApi(19) public class GridRowView extends androidx.slice.widget.SliceChildView implements android.view.View.OnClickListener android.view.View.OnTouchListener {
-    ctor public GridRowView(android.content.Context);
-    ctor public GridRowView(android.content.Context, android.util.AttributeSet?);
-    method protected boolean addImageItem(androidx.slice.SliceItem, androidx.slice.SliceItem?, int, android.view.ViewGroup, boolean);
-    method protected int getExtraBottomPadding();
-    method protected int getExtraTopPadding();
-    method protected int getMaxCells();
-    method protected int getTitleTextLayout();
+  @Deprecated @RequiresApi(19) public class GridRowView extends androidx.slice.widget.SliceChildView implements android.view.View.OnClickListener android.view.View.OnTouchListener {
+    ctor @Deprecated public GridRowView(android.content.Context);
+    ctor @Deprecated public GridRowView(android.content.Context, android.util.AttributeSet?);
+    method @Deprecated protected boolean addImageItem(androidx.slice.SliceItem, androidx.slice.SliceItem?, int, android.view.ViewGroup, boolean);
+    method @Deprecated protected int getExtraBottomPadding();
+    method @Deprecated protected int getExtraTopPadding();
+    method @Deprecated protected int getMaxCells();
+    method @Deprecated protected int getTitleTextLayout();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void onClick(android.view.View);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public boolean onTouch(android.view.View, android.view.MotionEvent);
-    method protected void populateViews();
-    method public void resetView();
-    method protected boolean scheduleMaxCellsUpdate();
+    method @Deprecated protected void populateViews();
+    method @Deprecated public void resetView();
+    method @Deprecated protected boolean scheduleMaxCellsUpdate();
   }
 
-  public interface RowStyleFactory {
-    method @StyleRes public int getRowStyleRes(androidx.slice.SliceItem);
+  @Deprecated public interface RowStyleFactory {
+    method @Deprecated @StyleRes public int getRowStyleRes(androidx.slice.SliceItem);
   }
 
-  @RequiresApi(19) public class RowView extends androidx.slice.widget.SliceChildView implements android.widget.AdapterView.OnItemSelectedListener android.view.View.OnClickListener {
-    ctor public RowView(android.content.Context);
-    method protected java.util.List<java.lang.String!> getEndItemKeys();
-    method protected androidx.slice.SliceItem? getPrimaryActionItem();
-    method protected String? getPrimaryActionKey();
-    method public void onClick(android.view.View);
-    method public void onItemSelected(android.widget.AdapterView<?>, android.view.View, int, long);
-    method public void onNothingSelected(android.widget.AdapterView<?>);
+  @Deprecated @RequiresApi(19) public class RowView extends androidx.slice.widget.SliceChildView implements android.widget.AdapterView.OnItemSelectedListener android.view.View.OnClickListener {
+    ctor @Deprecated public RowView(android.content.Context);
+    method @Deprecated protected java.util.List<java.lang.String!> getEndItemKeys();
+    method @Deprecated protected androidx.slice.SliceItem? getPrimaryActionItem();
+    method @Deprecated protected String? getPrimaryActionKey();
+    method @Deprecated public void onClick(android.view.View);
+    method @Deprecated public void onItemSelected(android.widget.AdapterView<?>, android.view.View, int, long);
+    method @Deprecated public void onNothingSelected(android.widget.AdapterView<?>);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void resetView();
   }
 
-  @RequiresApi(19) public class SliceAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.slice.widget.SliceAdapter.SliceViewHolder> {
-    ctor public SliceAdapter(android.content.Context);
-    method public androidx.slice.widget.GridRowView getGridRowView();
-    method public int getItemCount();
-    method public androidx.slice.widget.RowView getRowView();
+  @Deprecated @RequiresApi(19) public class SliceAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.slice.widget.SliceAdapter.SliceViewHolder> {
+    ctor @Deprecated public SliceAdapter(android.content.Context);
+    method @Deprecated public androidx.slice.widget.GridRowView getGridRowView();
+    method @Deprecated public int getItemCount();
+    method @Deprecated public androidx.slice.widget.RowView getRowView();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void onBindViewHolder(androidx.slice.widget.SliceAdapter.SliceViewHolder, int);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public androidx.slice.widget.SliceAdapter.SliceViewHolder onCreateViewHolder(android.view.ViewGroup, int);
   }
 
-  @RequiresApi(19) public abstract class SliceChildView extends android.widget.FrameLayout {
-    ctor public SliceChildView(android.content.Context);
-    ctor public SliceChildView(android.content.Context, android.util.AttributeSet?);
-    method public abstract void resetView();
-    method public void setSliceItem(androidx.slice.widget.SliceContent?, boolean, int, int, androidx.slice.widget.SliceView.OnSliceActionListener?);
+  @Deprecated @RequiresApi(19) public abstract class SliceChildView extends android.widget.FrameLayout {
+    ctor @Deprecated public SliceChildView(android.content.Context);
+    ctor @Deprecated public SliceChildView(android.content.Context, android.util.AttributeSet?);
+    method @Deprecated public abstract void resetView();
+    method @Deprecated public void setSliceItem(androidx.slice.widget.SliceContent?, boolean, int, int, androidx.slice.widget.SliceView.OnSliceActionListener?);
   }
 
-  @RequiresApi(19) public class SliceContent {
-    ctor public SliceContent(androidx.slice.Slice?);
+  @Deprecated @RequiresApi(19) public class SliceContent {
+    ctor @Deprecated public SliceContent(androidx.slice.Slice?);
   }
 
-  @RequiresApi(19) public final class SliceLiveData {
-    method public static androidx.slice.widget.SliceLiveData.CachedSliceLiveData fromCachedSlice(android.content.Context, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
-    method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromIntent(android.content.Context, android.content.Intent);
-    method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromIntent(android.content.Context, android.content.Intent, androidx.slice.widget.SliceLiveData.OnErrorListener?);
-    method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromStream(android.content.Context, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
-    method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri);
-    method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri, androidx.slice.widget.SliceLiveData.OnErrorListener?);
+  @Deprecated @RequiresApi(19) public final class SliceLiveData {
+    method @Deprecated public static androidx.slice.widget.SliceLiveData.CachedSliceLiveData fromCachedSlice(android.content.Context, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
+    method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromIntent(android.content.Context, android.content.Intent);
+    method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromIntent(android.content.Context, android.content.Intent, androidx.slice.widget.SliceLiveData.OnErrorListener?);
+    method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromStream(android.content.Context, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
+    method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri);
+    method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri, androidx.slice.widget.SliceLiveData.OnErrorListener?);
   }
 
-  public static class SliceLiveData.CachedSliceLiveData extends androidx.lifecycle.LiveData<androidx.slice.Slice> {
-    method public void goLive();
-    method public void parseStream();
+  @Deprecated public static class SliceLiveData.CachedSliceLiveData extends androidx.lifecycle.LiveData<androidx.slice.Slice> {
+    method @Deprecated public void goLive();
+    method @Deprecated public void parseStream();
   }
 
-  public static interface SliceLiveData.OnErrorListener {
-    method public void onSliceError(@androidx.slice.widget.SliceLiveData.OnErrorListener.ErrorType int, Throwable?);
-    field public static final int ERROR_INVALID_INPUT = 3; // 0x3
-    field public static final int ERROR_SLICE_NO_LONGER_PRESENT = 2; // 0x2
-    field public static final int ERROR_STRUCTURE_CHANGED = 1; // 0x1
-    field public static final int ERROR_UNKNOWN = 0; // 0x0
+  @Deprecated public static interface SliceLiveData.OnErrorListener {
+    method @Deprecated public void onSliceError(@androidx.slice.widget.SliceLiveData.OnErrorListener.ErrorType int, Throwable?);
+    field @Deprecated public static final int ERROR_INVALID_INPUT = 3; // 0x3
+    field @Deprecated public static final int ERROR_SLICE_NO_LONGER_PRESENT = 2; // 0x2
+    field @Deprecated public static final int ERROR_STRUCTURE_CHANGED = 1; // 0x1
+    field @Deprecated public static final int ERROR_UNKNOWN = 0; // 0x0
   }
 
-  @IntDef({androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_UNKNOWN, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_STRUCTURE_CHANGED, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_SLICE_NO_LONGER_PRESENT, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_INVALID_INPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SliceLiveData.OnErrorListener.ErrorType {
+  @Deprecated @IntDef({androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_UNKNOWN, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_STRUCTURE_CHANGED, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_SLICE_NO_LONGER_PRESENT, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_INVALID_INPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SliceLiveData.OnErrorListener.ErrorType {
   }
 
-  @RequiresApi(19) public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer<androidx.slice.Slice> android.view.View.OnClickListener {
-    ctor public SliceView(android.content.Context!);
-    ctor public SliceView(android.content.Context!, android.util.AttributeSet?);
-    ctor public SliceView(android.content.Context!, android.util.AttributeSet?, int);
-    ctor @RequiresApi(21) public SliceView(android.content.Context!, android.util.AttributeSet!, int, int);
-    method protected void configureViewPolicy(int);
-    method public int getHiddenItemCount();
-    method public int getMode();
-    method public androidx.slice.Slice? getSlice();
-    method public java.util.List<androidx.slice.core.SliceAction!>? getSliceActions();
-    method public boolean isScrollable();
-    method public void onChanged(androidx.slice.Slice?);
-    method public void onClick(android.view.View!);
-    method public void setAccentColor(@ColorInt int);
-    method public void setCurrentView(androidx.slice.widget.SliceChildView);
-    method public void setMode(int);
-    method public void setOnSliceActionListener(androidx.slice.widget.SliceView.OnSliceActionListener?);
-    method public void setRowStyleFactory(androidx.slice.widget.RowStyleFactory?);
-    method public void setScrollable(boolean);
-    method public void setShowActionDividers(boolean);
-    method public void setShowHeaderDivider(boolean);
-    method public void setShowTitleItems(boolean);
-    method public void setSlice(androidx.slice.Slice?);
-    method public void setSliceActions(java.util.List<androidx.slice.core.SliceAction!>?);
-    field public static final int MODE_LARGE = 2; // 0x2
-    field public static final int MODE_SHORTCUT = 3; // 0x3
-    field public static final int MODE_SMALL = 1; // 0x1
+  @Deprecated @RequiresApi(19) public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer<androidx.slice.Slice> android.view.View.OnClickListener {
+    ctor @Deprecated public SliceView(android.content.Context!);
+    ctor @Deprecated public SliceView(android.content.Context!, android.util.AttributeSet?);
+    ctor @Deprecated public SliceView(android.content.Context!, android.util.AttributeSet?, int);
+    ctor @Deprecated @RequiresApi(21) public SliceView(android.content.Context!, android.util.AttributeSet!, int, int);
+    method @Deprecated protected void configureViewPolicy(int);
+    method @Deprecated public int getHiddenItemCount();
+    method @Deprecated public int getMode();
+    method @Deprecated public androidx.slice.Slice? getSlice();
+    method @Deprecated public java.util.List<androidx.slice.core.SliceAction!>? getSliceActions();
+    method @Deprecated public boolean isScrollable();
+    method @Deprecated public void onChanged(androidx.slice.Slice?);
+    method @Deprecated public void onClick(android.view.View!);
+    method @Deprecated public void setAccentColor(@ColorInt int);
+    method @Deprecated public void setCurrentView(androidx.slice.widget.SliceChildView);
+    method @Deprecated public void setMode(int);
+    method @Deprecated public void setOnSliceActionListener(androidx.slice.widget.SliceView.OnSliceActionListener?);
+    method @Deprecated public void setRowStyleFactory(androidx.slice.widget.RowStyleFactory?);
+    method @Deprecated public void setScrollable(boolean);
+    method @Deprecated public void setShowActionDividers(boolean);
+    method @Deprecated public void setShowHeaderDivider(boolean);
+    method @Deprecated public void setShowTitleItems(boolean);
+    method @Deprecated public void setSlice(androidx.slice.Slice?);
+    method @Deprecated public void setSliceActions(java.util.List<androidx.slice.core.SliceAction!>?);
+    field @Deprecated public static final int MODE_LARGE = 2; // 0x2
+    field @Deprecated public static final int MODE_SHORTCUT = 3; // 0x3
+    field @Deprecated public static final int MODE_SMALL = 1; // 0x1
   }
 
-  public static interface SliceView.OnSliceActionListener {
-    method public void onSliceAction(androidx.slice.widget.EventInfo, androidx.slice.SliceItem);
+  @Deprecated public static interface SliceView.OnSliceActionListener {
+    method @Deprecated public void onSliceAction(androidx.slice.widget.EventInfo, androidx.slice.SliceItem);
   }
 
-  @RequiresApi(19) public class TemplateView extends androidx.slice.widget.SliceChildView {
-    ctor public TemplateView(android.content.Context);
-    method public void onAttachedToWindow();
+  @Deprecated @RequiresApi(19) public class TemplateView extends androidx.slice.widget.SliceChildView {
+    ctor @Deprecated public TemplateView(android.content.Context);
+    method @Deprecated public void onAttachedToWindow();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void resetView();
-    method public void setAdapter(androidx.slice.widget.SliceAdapter);
+    method @Deprecated public void setAdapter(androidx.slice.widget.SliceAdapter);
   }
 
 }
diff --git a/slice/slice-view/api/removed_current.txt b/slice/slice-view/api/removed_current.txt
index 83b9996..c72daba 100644
--- a/slice/slice-view/api/removed_current.txt
+++ b/slice/slice-view/api/removed_current.txt
@@ -1,7 +1,7 @@
 // Signature format: 4.0
 package androidx.slice.widget {
 
-  @RequiresApi(19) public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer<androidx.slice.Slice> android.view.View.OnClickListener {
+  @Deprecated @RequiresApi(19) public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer<androidx.slice.Slice> android.view.View.OnClickListener {
     method @Deprecated public void showActionDividers(boolean);
     method @Deprecated public void showHeaderDivider(boolean);
     method @Deprecated public void showTitleItems(boolean);
diff --git a/slice/slice-view/api/restricted_current.txt b/slice/slice-view/api/restricted_current.txt
index 9f3c43a..068889b 100644
--- a/slice/slice-view/api/restricted_current.txt
+++ b/slice/slice-view/api/restricted_current.txt
@@ -1,294 +1,294 @@
 // Signature format: 4.0
 package androidx.slice {
 
-  @RequiresApi(19) public class SliceMetadata {
-    method public static androidx.slice.SliceMetadata from(android.content.Context?, androidx.slice.Slice);
-    method public long getExpiry();
-    method public int getHeaderType();
-    method public android.os.Bundle getHostExtras();
-    method public android.app.PendingIntent? getInputRangeAction();
-    method public long getLastUpdatedTime();
-    method public int getLoadingState();
-    method public androidx.slice.core.SliceAction? getPrimaryAction();
-    method public androidx.core.util.Pair<java.lang.Integer!,java.lang.Integer!>? getRange();
-    method public int getRangeValue();
-    method public java.util.List<androidx.slice.core.SliceAction!>? getSliceActions();
-    method public java.util.List<java.lang.String!>? getSliceKeywords();
-    method public CharSequence? getSubtitle();
-    method public CharSequence? getSummary();
-    method public CharSequence? getTitle();
-    method public java.util.List<androidx.slice.core.SliceAction!>! getToggles();
-    method public boolean hasLargeMode();
-    method public boolean isCachedSlice();
-    method public boolean isErrorSlice();
-    method public boolean isPermissionSlice();
-    method public boolean isSelection();
-    method public boolean sendInputRangeAction(int) throws android.app.PendingIntent.CanceledException;
-    method public boolean sendToggleAction(androidx.slice.core.SliceAction!, boolean) throws android.app.PendingIntent.CanceledException;
-    field public static final int LOADED_ALL = 2; // 0x2
-    field public static final int LOADED_NONE = 0; // 0x0
-    field public static final int LOADED_PARTIAL = 1; // 0x1
+  @Deprecated @RequiresApi(19) public class SliceMetadata {
+    method @Deprecated public static androidx.slice.SliceMetadata from(android.content.Context?, androidx.slice.Slice);
+    method @Deprecated public long getExpiry();
+    method @Deprecated public int getHeaderType();
+    method @Deprecated public android.os.Bundle getHostExtras();
+    method @Deprecated public android.app.PendingIntent? getInputRangeAction();
+    method @Deprecated public long getLastUpdatedTime();
+    method @Deprecated public int getLoadingState();
+    method @Deprecated public androidx.slice.core.SliceAction? getPrimaryAction();
+    method @Deprecated public androidx.core.util.Pair<java.lang.Integer!,java.lang.Integer!>? getRange();
+    method @Deprecated public int getRangeValue();
+    method @Deprecated public java.util.List<androidx.slice.core.SliceAction!>? getSliceActions();
+    method @Deprecated public java.util.List<java.lang.String!>? getSliceKeywords();
+    method @Deprecated public CharSequence? getSubtitle();
+    method @Deprecated public CharSequence? getSummary();
+    method @Deprecated public CharSequence? getTitle();
+    method @Deprecated public java.util.List<androidx.slice.core.SliceAction!>! getToggles();
+    method @Deprecated public boolean hasLargeMode();
+    method @Deprecated public boolean isCachedSlice();
+    method @Deprecated public boolean isErrorSlice();
+    method @Deprecated public boolean isPermissionSlice();
+    method @Deprecated public boolean isSelection();
+    method @Deprecated public boolean sendInputRangeAction(int) throws android.app.PendingIntent.CanceledException;
+    method @Deprecated public boolean sendToggleAction(androidx.slice.core.SliceAction!, boolean) throws android.app.PendingIntent.CanceledException;
+    field @Deprecated public static final int LOADED_ALL = 2; // 0x2
+    field @Deprecated public static final int LOADED_NONE = 0; // 0x0
+    field @Deprecated public static final int LOADED_PARTIAL = 1; // 0x1
   }
 
-  @RequiresApi(19) public class SliceStructure {
-    ctor public SliceStructure(androidx.slice.Slice!);
+  @Deprecated @RequiresApi(19) public class SliceStructure {
+    ctor @Deprecated public SliceStructure(androidx.slice.Slice!);
   }
 
-  @RequiresApi(19) public class SliceUtils {
-    method public static androidx.slice.Slice parseSlice(android.content.Context, java.io.InputStream, String, androidx.slice.SliceUtils.SliceActionListener) throws java.io.IOException, androidx.slice.SliceUtils.SliceParseException;
-    method public static void serializeSlice(androidx.slice.Slice, android.content.Context, java.io.OutputStream, androidx.slice.SliceUtils.SerializeOptions) throws java.lang.IllegalArgumentException;
-    method public static androidx.slice.Slice stripSlice(androidx.slice.Slice, int, boolean);
+  @Deprecated @RequiresApi(19) public class SliceUtils {
+    method @Deprecated public static androidx.slice.Slice parseSlice(android.content.Context, java.io.InputStream, String, androidx.slice.SliceUtils.SliceActionListener) throws java.io.IOException, androidx.slice.SliceUtils.SliceParseException;
+    method @Deprecated public static void serializeSlice(androidx.slice.Slice, android.content.Context, java.io.OutputStream, androidx.slice.SliceUtils.SerializeOptions) throws java.lang.IllegalArgumentException;
+    method @Deprecated public static androidx.slice.Slice stripSlice(androidx.slice.Slice, int, boolean);
   }
 
-  public static class SliceUtils.SerializeOptions {
-    ctor public SliceUtils.SerializeOptions();
-    method public androidx.slice.SliceUtils.SerializeOptions! setActionMode(int);
-    method public androidx.slice.SliceUtils.SerializeOptions! setImageConversionFormat(android.graphics.Bitmap.CompressFormat!, int);
-    method public androidx.slice.SliceUtils.SerializeOptions! setImageMode(int);
-    method public androidx.slice.SliceUtils.SerializeOptions! setMaxImageHeight(int);
-    method public androidx.slice.SliceUtils.SerializeOptions! setMaxImageWidth(int);
-    field public static final int MODE_CONVERT = 2; // 0x2
-    field public static final int MODE_REMOVE = 1; // 0x1
-    field public static final int MODE_THROW = 0; // 0x0
+  @Deprecated public static class SliceUtils.SerializeOptions {
+    ctor @Deprecated public SliceUtils.SerializeOptions();
+    method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setActionMode(int);
+    method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setImageConversionFormat(android.graphics.Bitmap.CompressFormat!, int);
+    method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setImageMode(int);
+    method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setMaxImageHeight(int);
+    method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setMaxImageWidth(int);
+    field @Deprecated public static final int MODE_CONVERT = 2; // 0x2
+    field @Deprecated public static final int MODE_REMOVE = 1; // 0x1
+    field @Deprecated public static final int MODE_THROW = 0; // 0x0
   }
 
-  public static interface SliceUtils.SliceActionListener {
-    method public void onSliceAction(android.net.Uri!, android.content.Context!, android.content.Intent!);
+  @Deprecated public static interface SliceUtils.SliceActionListener {
+    method @Deprecated public void onSliceAction(android.net.Uri!, android.content.Context!, android.content.Intent!);
   }
 
-  public static class SliceUtils.SliceParseException extends java.lang.Exception {
+  @Deprecated public static class SliceUtils.SliceParseException extends java.lang.Exception {
   }
 
-  @RequiresApi(19) public abstract class SliceViewManager {
-    method public abstract androidx.slice.Slice? bindSlice(android.content.Intent);
-    method public abstract androidx.slice.Slice? bindSlice(android.net.Uri);
-    method public static androidx.slice.SliceViewManager getInstance(android.content.Context);
-    method @WorkerThread public abstract java.util.Collection<android.net.Uri!> getSliceDescendants(android.net.Uri);
-    method public abstract android.net.Uri? mapIntentToUri(android.content.Intent);
-    method public abstract void pinSlice(android.net.Uri);
-    method public abstract void registerSliceCallback(android.net.Uri, androidx.slice.SliceViewManager.SliceCallback);
-    method public abstract void registerSliceCallback(android.net.Uri, java.util.concurrent.Executor, androidx.slice.SliceViewManager.SliceCallback);
-    method public abstract void unpinSlice(android.net.Uri);
-    method public abstract void unregisterSliceCallback(android.net.Uri, androidx.slice.SliceViewManager.SliceCallback);
+  @Deprecated @RequiresApi(19) public abstract class SliceViewManager {
+    method @Deprecated public abstract androidx.slice.Slice? bindSlice(android.content.Intent);
+    method @Deprecated public abstract androidx.slice.Slice? bindSlice(android.net.Uri);
+    method @Deprecated public static androidx.slice.SliceViewManager getInstance(android.content.Context);
+    method @Deprecated @WorkerThread public abstract java.util.Collection<android.net.Uri!> getSliceDescendants(android.net.Uri);
+    method @Deprecated public abstract android.net.Uri? mapIntentToUri(android.content.Intent);
+    method @Deprecated public abstract void pinSlice(android.net.Uri);
+    method @Deprecated public abstract void registerSliceCallback(android.net.Uri, androidx.slice.SliceViewManager.SliceCallback);
+    method @Deprecated public abstract void registerSliceCallback(android.net.Uri, java.util.concurrent.Executor, androidx.slice.SliceViewManager.SliceCallback);
+    method @Deprecated public abstract void unpinSlice(android.net.Uri);
+    method @Deprecated public abstract void unregisterSliceCallback(android.net.Uri, androidx.slice.SliceViewManager.SliceCallback);
   }
 
-  public static interface SliceViewManager.SliceCallback {
-    method public void onSliceUpdated(androidx.slice.Slice?);
+  @Deprecated public static interface SliceViewManager.SliceCallback {
+    method @Deprecated public void onSliceUpdated(androidx.slice.Slice?);
   }
 
 }
 
 package androidx.slice.widget {
 
-  @RequiresApi(19) public class EventInfo {
-    ctor public EventInfo(int, int, int, int);
-    method public void setPosition(int, int, int);
-    field public static final int ACTION_TYPE_BUTTON = 1; // 0x1
-    field public static final int ACTION_TYPE_CONTENT = 3; // 0x3
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final int ACTION_TYPE_DATE_PICK = 6; // 0x6
-    field public static final int ACTION_TYPE_SEE_MORE = 4; // 0x4
-    field public static final int ACTION_TYPE_SELECTION = 5; // 0x5
-    field public static final int ACTION_TYPE_SLIDER = 2; // 0x2
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final int ACTION_TYPE_TIME_PICK = 7; // 0x7
-    field public static final int ACTION_TYPE_TOGGLE = 0; // 0x0
-    field public static final int POSITION_CELL = 2; // 0x2
-    field public static final int POSITION_END = 1; // 0x1
-    field public static final int POSITION_START = 0; // 0x0
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final int ROW_TYPE_DATE_PICK = 7; // 0x7
-    field public static final int ROW_TYPE_GRID = 1; // 0x1
-    field public static final int ROW_TYPE_LIST = 0; // 0x0
-    field public static final int ROW_TYPE_MESSAGING = 2; // 0x2
-    field public static final int ROW_TYPE_PROGRESS = 5; // 0x5
-    field public static final int ROW_TYPE_SELECTION = 6; // 0x6
-    field public static final int ROW_TYPE_SHORTCUT = -1; // 0xffffffff
-    field public static final int ROW_TYPE_SLIDER = 4; // 0x4
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final int ROW_TYPE_TIME_PICK = 8; // 0x8
-    field public static final int ROW_TYPE_TOGGLE = 3; // 0x3
-    field public static final int STATE_OFF = 0; // 0x0
-    field public static final int STATE_ON = 1; // 0x1
-    field public int actionCount;
-    field public int actionIndex;
-    field public int actionPosition;
-    field public int actionType;
-    field public int rowIndex;
-    field public int rowTemplateType;
-    field public int sliceMode;
-    field public int state;
+  @Deprecated @RequiresApi(19) public class EventInfo {
+    ctor @Deprecated public EventInfo(int, int, int, int);
+    method @Deprecated public void setPosition(int, int, int);
+    field @Deprecated public static final int ACTION_TYPE_BUTTON = 1; // 0x1
+    field @Deprecated public static final int ACTION_TYPE_CONTENT = 3; // 0x3
+    field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final int ACTION_TYPE_DATE_PICK = 6; // 0x6
+    field @Deprecated public static final int ACTION_TYPE_SEE_MORE = 4; // 0x4
+    field @Deprecated public static final int ACTION_TYPE_SELECTION = 5; // 0x5
+    field @Deprecated public static final int ACTION_TYPE_SLIDER = 2; // 0x2
+    field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final int ACTION_TYPE_TIME_PICK = 7; // 0x7
+    field @Deprecated public static final int ACTION_TYPE_TOGGLE = 0; // 0x0
+    field @Deprecated public static final int POSITION_CELL = 2; // 0x2
+    field @Deprecated public static final int POSITION_END = 1; // 0x1
+    field @Deprecated public static final int POSITION_START = 0; // 0x0
+    field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final int ROW_TYPE_DATE_PICK = 7; // 0x7
+    field @Deprecated public static final int ROW_TYPE_GRID = 1; // 0x1
+    field @Deprecated public static final int ROW_TYPE_LIST = 0; // 0x0
+    field @Deprecated public static final int ROW_TYPE_MESSAGING = 2; // 0x2
+    field @Deprecated public static final int ROW_TYPE_PROGRESS = 5; // 0x5
+    field @Deprecated public static final int ROW_TYPE_SELECTION = 6; // 0x6
+    field @Deprecated public static final int ROW_TYPE_SHORTCUT = -1; // 0xffffffff
+    field @Deprecated public static final int ROW_TYPE_SLIDER = 4; // 0x4
+    field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final int ROW_TYPE_TIME_PICK = 8; // 0x8
+    field @Deprecated public static final int ROW_TYPE_TOGGLE = 3; // 0x3
+    field @Deprecated public static final int STATE_OFF = 0; // 0x0
+    field @Deprecated public static final int STATE_ON = 1; // 0x1
+    field @Deprecated public int actionCount;
+    field @Deprecated public int actionIndex;
+    field @Deprecated public int actionPosition;
+    field @Deprecated public int actionType;
+    field @Deprecated public int rowIndex;
+    field @Deprecated public int rowTemplateType;
+    field @Deprecated public int sliceMode;
+    field @Deprecated public int state;
   }
 
-  @RequiresApi(19) public class GridContent extends androidx.slice.widget.SliceContent {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public GridContent(androidx.slice.SliceItem, int);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.SliceItem? getContentIntent();
-    method public android.graphics.Point getFirstImageSize(android.content.Context);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public java.util.ArrayList<androidx.slice.widget.GridContent.CellContent!> getGridContent();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean getIsLastIndex();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getLargestImageMode();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getMaxCellLineCount();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.SliceItem? getSeeMoreItem();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public CharSequence? getTitle();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean hasImage();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isAllImages();
-    method public boolean isValid();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setIsLastIndex(boolean);
+  @Deprecated @RequiresApi(19) public class GridContent extends androidx.slice.widget.SliceContent {
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public GridContent(androidx.slice.SliceItem, int);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.SliceItem? getContentIntent();
+    method @Deprecated public android.graphics.Point getFirstImageSize(android.content.Context);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public java.util.ArrayList<androidx.slice.widget.GridContent.CellContent!> getGridContent();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean getIsLastIndex();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getLargestImageMode();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getMaxCellLineCount();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.SliceItem? getSeeMoreItem();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public CharSequence? getTitle();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean hasImage();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isAllImages();
+    method @Deprecated public boolean isValid();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setIsLastIndex(boolean);
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class GridContent.CellContent {
-    ctor public GridContent.CellContent(androidx.slice.SliceItem);
-    method public java.util.ArrayList<androidx.slice.SliceItem!> getCellItems();
-    method public CharSequence? getContentDescription();
-    method public androidx.slice.SliceItem? getContentIntent();
-    method public androidx.core.graphics.drawable.IconCompat? getImageIcon();
-    method public int getImageMode();
-    method public androidx.slice.SliceItem? getOverlayItem();
-    method public androidx.slice.SliceItem? getPicker();
-    method public int getTextCount();
-    method public androidx.slice.SliceItem? getTitleItem();
-    method public androidx.slice.SliceItem? getToggleItem();
-    method public boolean hasImage();
-    method public boolean isImageOnly();
-    method public boolean isValid();
-    method public boolean populate(androidx.slice.SliceItem);
+  @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class GridContent.CellContent {
+    ctor @Deprecated public GridContent.CellContent(androidx.slice.SliceItem);
+    method @Deprecated public java.util.ArrayList<androidx.slice.SliceItem!> getCellItems();
+    method @Deprecated public CharSequence? getContentDescription();
+    method @Deprecated public androidx.slice.SliceItem? getContentIntent();
+    method @Deprecated public androidx.core.graphics.drawable.IconCompat? getImageIcon();
+    method @Deprecated public int getImageMode();
+    method @Deprecated public androidx.slice.SliceItem? getOverlayItem();
+    method @Deprecated public androidx.slice.SliceItem? getPicker();
+    method @Deprecated public int getTextCount();
+    method @Deprecated public androidx.slice.SliceItem? getTitleItem();
+    method @Deprecated public androidx.slice.SliceItem? getToggleItem();
+    method @Deprecated public boolean hasImage();
+    method @Deprecated public boolean isImageOnly();
+    method @Deprecated public boolean isValid();
+    method @Deprecated public boolean populate(androidx.slice.SliceItem);
   }
 
-  @RequiresApi(19) public class GridRowView extends androidx.slice.widget.SliceChildView implements android.view.View.OnClickListener android.view.View.OnTouchListener {
-    ctor public GridRowView(android.content.Context);
-    ctor public GridRowView(android.content.Context, android.util.AttributeSet?);
-    method protected boolean addImageItem(androidx.slice.SliceItem, androidx.slice.SliceItem?, int, android.view.ViewGroup, boolean);
-    method protected int getExtraBottomPadding();
-    method protected int getExtraTopPadding();
-    method protected int getMaxCells();
-    method protected int getTitleTextLayout();
+  @Deprecated @RequiresApi(19) public class GridRowView extends androidx.slice.widget.SliceChildView implements android.view.View.OnClickListener android.view.View.OnTouchListener {
+    ctor @Deprecated public GridRowView(android.content.Context);
+    ctor @Deprecated public GridRowView(android.content.Context, android.util.AttributeSet?);
+    method @Deprecated protected boolean addImageItem(androidx.slice.SliceItem, androidx.slice.SliceItem?, int, android.view.ViewGroup, boolean);
+    method @Deprecated protected int getExtraBottomPadding();
+    method @Deprecated protected int getExtraTopPadding();
+    method @Deprecated protected int getMaxCells();
+    method @Deprecated protected int getTitleTextLayout();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void onClick(android.view.View);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public boolean onTouch(android.view.View, android.view.MotionEvent);
-    method protected void populateViews();
-    method public void resetView();
-    method protected boolean scheduleMaxCellsUpdate();
+    method @Deprecated protected void populateViews();
+    method @Deprecated public void resetView();
+    method @Deprecated protected boolean scheduleMaxCellsUpdate();
   }
 
-  @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RowContent extends androidx.slice.widget.SliceContent {
-    ctor public RowContent(androidx.slice.SliceItem!, int);
-    method public java.util.List<androidx.slice.SliceItem!>! getEndItems();
-    method public androidx.slice.SliceItem? getInputRangeThumb();
-    method public boolean getIsHeader();
-    method public int getLineCount();
-    method public androidx.slice.SliceItem? getPrimaryAction();
-    method public androidx.slice.SliceItem? getRange();
-    method public androidx.slice.SliceItem? getSelection();
-    method public androidx.slice.SliceItem? getStartItem();
-    method public androidx.slice.SliceItem? getSubtitleItem();
-    method public androidx.slice.SliceItem? getSummaryItem();
-    method public androidx.slice.SliceItem? getTitleItem();
-    method public java.util.List<androidx.slice.core.SliceAction!>! getToggleItems();
-    method public boolean hasActionDivider();
-    method public boolean hasBottomDivider();
-    method public boolean hasTitleItems();
-    method public boolean isDefaultSeeMore();
-    method public boolean isValid();
-    method public void setIsHeader(boolean);
-    method public void showActionDivider(boolean);
-    method public void showBottomDivider(boolean);
-    method public void showTitleItems(boolean);
+  @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RowContent extends androidx.slice.widget.SliceContent {
+    ctor @Deprecated public RowContent(androidx.slice.SliceItem!, int);
+    method @Deprecated public java.util.List<androidx.slice.SliceItem!>! getEndItems();
+    method @Deprecated public androidx.slice.SliceItem? getInputRangeThumb();
+    method @Deprecated public boolean getIsHeader();
+    method @Deprecated public int getLineCount();
+    method @Deprecated public androidx.slice.SliceItem? getPrimaryAction();
+    method @Deprecated public androidx.slice.SliceItem? getRange();
+    method @Deprecated public androidx.slice.SliceItem? getSelection();
+    method @Deprecated public androidx.slice.SliceItem? getStartItem();
+    method @Deprecated public androidx.slice.SliceItem? getSubtitleItem();
+    method @Deprecated public androidx.slice.SliceItem? getSummaryItem();
+    method @Deprecated public androidx.slice.SliceItem? getTitleItem();
+    method @Deprecated public java.util.List<androidx.slice.core.SliceAction!>! getToggleItems();
+    method @Deprecated public boolean hasActionDivider();
+    method @Deprecated public boolean hasBottomDivider();
+    method @Deprecated public boolean hasTitleItems();
+    method @Deprecated public boolean isDefaultSeeMore();
+    method @Deprecated public boolean isValid();
+    method @Deprecated public void setIsHeader(boolean);
+    method @Deprecated public void showActionDivider(boolean);
+    method @Deprecated public void showBottomDivider(boolean);
+    method @Deprecated public void showTitleItems(boolean);
   }
 
-  public interface RowStyleFactory {
-    method @StyleRes public int getRowStyleRes(androidx.slice.SliceItem);
+  @Deprecated public interface RowStyleFactory {
+    method @Deprecated @StyleRes public int getRowStyleRes(androidx.slice.SliceItem);
   }
 
-  @RequiresApi(19) public class RowView extends androidx.slice.widget.SliceChildView implements android.widget.AdapterView.OnItemSelectedListener android.view.View.OnClickListener {
-    ctor public RowView(android.content.Context);
-    method protected java.util.List<java.lang.String!> getEndItemKeys();
-    method protected androidx.slice.SliceItem? getPrimaryActionItem();
-    method protected String? getPrimaryActionKey();
-    method public void onClick(android.view.View);
-    method public void onItemSelected(android.widget.AdapterView<?>, android.view.View, int, long);
-    method public void onNothingSelected(android.widget.AdapterView<?>);
+  @Deprecated @RequiresApi(19) public class RowView extends androidx.slice.widget.SliceChildView implements android.widget.AdapterView.OnItemSelectedListener android.view.View.OnClickListener {
+    ctor @Deprecated public RowView(android.content.Context);
+    method @Deprecated protected java.util.List<java.lang.String!> getEndItemKeys();
+    method @Deprecated protected androidx.slice.SliceItem? getPrimaryActionItem();
+    method @Deprecated protected String? getPrimaryActionKey();
+    method @Deprecated public void onClick(android.view.View);
+    method @Deprecated public void onItemSelected(android.widget.AdapterView<?>, android.view.View, int, long);
+    method @Deprecated public void onNothingSelected(android.widget.AdapterView<?>);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void resetView();
   }
 
-  @RequiresApi(19) public class SliceAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.slice.widget.SliceAdapter.SliceViewHolder> {
-    ctor public SliceAdapter(android.content.Context);
-    method public androidx.slice.widget.GridRowView getGridRowView();
-    method public int getItemCount();
-    method public androidx.slice.widget.RowView getRowView();
+  @Deprecated @RequiresApi(19) public class SliceAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.slice.widget.SliceAdapter.SliceViewHolder> {
+    ctor @Deprecated public SliceAdapter(android.content.Context);
+    method @Deprecated public androidx.slice.widget.GridRowView getGridRowView();
+    method @Deprecated public int getItemCount();
+    method @Deprecated public androidx.slice.widget.RowView getRowView();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void onBindViewHolder(androidx.slice.widget.SliceAdapter.SliceViewHolder, int);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public androidx.slice.widget.SliceAdapter.SliceViewHolder onCreateViewHolder(android.view.ViewGroup, int);
   }
 
-  @RequiresApi(19) public abstract class SliceChildView extends android.widget.FrameLayout {
-    ctor public SliceChildView(android.content.Context);
-    ctor public SliceChildView(android.content.Context, android.util.AttributeSet?);
-    method public abstract void resetView();
-    method public void setSliceItem(androidx.slice.widget.SliceContent?, boolean, int, int, androidx.slice.widget.SliceView.OnSliceActionListener?);
+  @Deprecated @RequiresApi(19) public abstract class SliceChildView extends android.widget.FrameLayout {
+    ctor @Deprecated public SliceChildView(android.content.Context);
+    ctor @Deprecated public SliceChildView(android.content.Context, android.util.AttributeSet?);
+    method @Deprecated public abstract void resetView();
+    method @Deprecated public void setSliceItem(androidx.slice.widget.SliceContent?, boolean, int, int, androidx.slice.widget.SliceView.OnSliceActionListener?);
   }
 
-  @RequiresApi(19) public class SliceContent {
-    ctor public SliceContent(androidx.slice.Slice?);
+  @Deprecated @RequiresApi(19) public class SliceContent {
+    ctor @Deprecated public SliceContent(androidx.slice.Slice?);
   }
 
-  @RequiresApi(19) public final class SliceLiveData {
-    method public static androidx.slice.widget.SliceLiveData.CachedSliceLiveData fromCachedSlice(android.content.Context, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
-    method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromIntent(android.content.Context, android.content.Intent);
-    method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromIntent(android.content.Context, android.content.Intent, androidx.slice.widget.SliceLiveData.OnErrorListener?);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.slice.widget.SliceLiveData.CachedSliceLiveData fromStream(android.content.Context, androidx.slice.SliceViewManager!, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
-    method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromStream(android.content.Context, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
-    method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri);
-    method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri, androidx.slice.widget.SliceLiveData.OnErrorListener?);
+  @Deprecated @RequiresApi(19) public final class SliceLiveData {
+    method @Deprecated public static androidx.slice.widget.SliceLiveData.CachedSliceLiveData fromCachedSlice(android.content.Context, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
+    method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromIntent(android.content.Context, android.content.Intent);
+    method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromIntent(android.content.Context, android.content.Intent, androidx.slice.widget.SliceLiveData.OnErrorListener?);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.slice.widget.SliceLiveData.CachedSliceLiveData fromStream(android.content.Context, androidx.slice.SliceViewManager!, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
+    method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromStream(android.content.Context, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
+    method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri);
+    method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri, androidx.slice.widget.SliceLiveData.OnErrorListener?);
   }
 
-  public static class SliceLiveData.CachedSliceLiveData extends androidx.lifecycle.LiveData<androidx.slice.Slice> {
-    method public void goLive();
-    method public void parseStream();
+  @Deprecated public static class SliceLiveData.CachedSliceLiveData extends androidx.lifecycle.LiveData<androidx.slice.Slice> {
+    method @Deprecated public void goLive();
+    method @Deprecated public void parseStream();
   }
 
-  public static interface SliceLiveData.OnErrorListener {
-    method public void onSliceError(@androidx.slice.widget.SliceLiveData.OnErrorListener.ErrorType int, Throwable?);
-    field public static final int ERROR_INVALID_INPUT = 3; // 0x3
-    field public static final int ERROR_SLICE_NO_LONGER_PRESENT = 2; // 0x2
-    field public static final int ERROR_STRUCTURE_CHANGED = 1; // 0x1
-    field public static final int ERROR_UNKNOWN = 0; // 0x0
+  @Deprecated public static interface SliceLiveData.OnErrorListener {
+    method @Deprecated public void onSliceError(@androidx.slice.widget.SliceLiveData.OnErrorListener.ErrorType int, Throwable?);
+    field @Deprecated public static final int ERROR_INVALID_INPUT = 3; // 0x3
+    field @Deprecated public static final int ERROR_SLICE_NO_LONGER_PRESENT = 2; // 0x2
+    field @Deprecated public static final int ERROR_STRUCTURE_CHANGED = 1; // 0x1
+    field @Deprecated public static final int ERROR_UNKNOWN = 0; // 0x0
   }
 
-  @IntDef({androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_UNKNOWN, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_STRUCTURE_CHANGED, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_SLICE_NO_LONGER_PRESENT, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_INVALID_INPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SliceLiveData.OnErrorListener.ErrorType {
+  @Deprecated @IntDef({androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_UNKNOWN, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_STRUCTURE_CHANGED, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_SLICE_NO_LONGER_PRESENT, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_INVALID_INPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SliceLiveData.OnErrorListener.ErrorType {
   }
 
-  @RequiresApi(19) public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer<androidx.slice.Slice> android.view.View.OnClickListener {
-    ctor public SliceView(android.content.Context!);
-    ctor public SliceView(android.content.Context!, android.util.AttributeSet?);
-    ctor public SliceView(android.content.Context!, android.util.AttributeSet?, int);
-    ctor @RequiresApi(21) public SliceView(android.content.Context!, android.util.AttributeSet!, int, int);
-    method protected void configureViewPolicy(int);
-    method public int getHiddenItemCount();
-    method public int getMode();
-    method public androidx.slice.Slice? getSlice();
-    method public java.util.List<androidx.slice.core.SliceAction!>? getSliceActions();
-    method public boolean isScrollable();
-    method public void onChanged(androidx.slice.Slice?);
-    method public void onClick(android.view.View!);
-    method public void setAccentColor(@ColorInt int);
-    method public void setCurrentView(androidx.slice.widget.SliceChildView);
-    method public void setMode(int);
-    method public void setOnSliceActionListener(androidx.slice.widget.SliceView.OnSliceActionListener?);
-    method public void setRowStyleFactory(androidx.slice.widget.RowStyleFactory?);
-    method public void setScrollable(boolean);
-    method public void setShowActionDividers(boolean);
-    method public void setShowHeaderDivider(boolean);
-    method public void setShowTitleItems(boolean);
-    method public void setSlice(androidx.slice.Slice?);
-    method public void setSliceActions(java.util.List<androidx.slice.core.SliceAction!>?);
-    field public static final int MODE_LARGE = 2; // 0x2
-    field public static final int MODE_SHORTCUT = 3; // 0x3
-    field public static final int MODE_SMALL = 1; // 0x1
+  @Deprecated @RequiresApi(19) public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer<androidx.slice.Slice> android.view.View.OnClickListener {
+    ctor @Deprecated public SliceView(android.content.Context!);
+    ctor @Deprecated public SliceView(android.content.Context!, android.util.AttributeSet?);
+    ctor @Deprecated public SliceView(android.content.Context!, android.util.AttributeSet?, int);
+    ctor @Deprecated @RequiresApi(21) public SliceView(android.content.Context!, android.util.AttributeSet!, int, int);
+    method @Deprecated protected void configureViewPolicy(int);
+    method @Deprecated public int getHiddenItemCount();
+    method @Deprecated public int getMode();
+    method @Deprecated public androidx.slice.Slice? getSlice();
+    method @Deprecated public java.util.List<androidx.slice.core.SliceAction!>? getSliceActions();
+    method @Deprecated public boolean isScrollable();
+    method @Deprecated public void onChanged(androidx.slice.Slice?);
+    method @Deprecated public void onClick(android.view.View!);
+    method @Deprecated public void setAccentColor(@ColorInt int);
+    method @Deprecated public void setCurrentView(androidx.slice.widget.SliceChildView);
+    method @Deprecated public void setMode(int);
+    method @Deprecated public void setOnSliceActionListener(androidx.slice.widget.SliceView.OnSliceActionListener?);
+    method @Deprecated public void setRowStyleFactory(androidx.slice.widget.RowStyleFactory?);
+    method @Deprecated public void setScrollable(boolean);
+    method @Deprecated public void setShowActionDividers(boolean);
+    method @Deprecated public void setShowHeaderDivider(boolean);
+    method @Deprecated public void setShowTitleItems(boolean);
+    method @Deprecated public void setSlice(androidx.slice.Slice?);
+    method @Deprecated public void setSliceActions(java.util.List<androidx.slice.core.SliceAction!>?);
+    field @Deprecated public static final int MODE_LARGE = 2; // 0x2
+    field @Deprecated public static final int MODE_SHORTCUT = 3; // 0x3
+    field @Deprecated public static final int MODE_SMALL = 1; // 0x1
   }
 
-  public static interface SliceView.OnSliceActionListener {
-    method public void onSliceAction(androidx.slice.widget.EventInfo, androidx.slice.SliceItem);
+  @Deprecated public static interface SliceView.OnSliceActionListener {
+    method @Deprecated public void onSliceAction(androidx.slice.widget.EventInfo, androidx.slice.SliceItem);
   }
 
-  @RequiresApi(19) public class TemplateView extends androidx.slice.widget.SliceChildView {
-    ctor public TemplateView(android.content.Context);
-    method public void onAttachedToWindow();
+  @Deprecated @RequiresApi(19) public class TemplateView extends androidx.slice.widget.SliceChildView {
+    ctor @Deprecated public TemplateView(android.content.Context);
+    method @Deprecated public void onAttachedToWindow();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void resetView();
-    method public void setAdapter(androidx.slice.widget.SliceAdapter);
+    method @Deprecated public void setAdapter(androidx.slice.widget.SliceAdapter);
   }
 
 }
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceMetadata.java b/slice/slice-view/src/main/java/androidx/slice/SliceMetadata.java
index edbc303..45009c3 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceMetadata.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceMetadata.java
@@ -71,8 +71,13 @@
 
 /**
  * Utility class to parse a {@link Slice} and provide access to information around its contents.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public class SliceMetadata {
 
     /**
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceStructure.java b/slice/slice-view/src/main/java/androidx/slice/SliceStructure.java
index f4f1f27..4b9f7cd 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceStructure.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceStructure.java
@@ -37,8 +37,13 @@
  * specific content such as text or icons.
  *
  * Two structures can be compared using {@link #equals(Object)}.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public class SliceStructure {
 
     private final String mStructure;
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceUtils.java b/slice/slice-view/src/main/java/androidx/slice/SliceUtils.java
index 46f1d35..d6209ce 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceUtils.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceUtils.java
@@ -63,8 +63,13 @@
 
 /**
  * Utilities for dealing with slices.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public class SliceUtils {
 
     private SliceUtils() {
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceViewManager.java b/slice/slice-view/src/main/java/androidx/slice/SliceViewManager.java
index 95c0d6d..3ebb7de 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceViewManager.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceViewManager.java
@@ -34,8 +34,13 @@
  * Class to handle interactions with {@link Slice}s.
  * <p>
  * The SliceViewManager manages permissions and pinned state for slices.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public abstract class SliceViewManager {
 
     /**
@@ -183,7 +188,12 @@
 
     /**
      * Class that listens to changes in {@link Slice}s.
+     *
+     * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+     * forward. If you are looking for a framework that handles communication across apps,
+     * consider using {@link android.app.appsearch.AppSearchManager}.
      */
+    @Deprecated
     public interface SliceCallback {
 
         /**
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerBase.java b/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerBase.java
index 217d0f17d..b7a5750 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerBase.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerBase.java
@@ -37,6 +37,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public abstract class SliceViewManagerBase extends SliceViewManager {
     private final ArrayMap<Pair<Uri, SliceCallback>, SliceListenerImpl> mListenerLookup =
             new ArrayMap<>();
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerCompat.java b/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerCompat.java
index 1c1d436..02bfec0 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerCompat.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerCompat.java
@@ -36,6 +36,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
+@Deprecated
 class SliceViewManagerCompat extends SliceViewManagerBase {
 
     SliceViewManagerCompat(Context context) {
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerWrapper.java b/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerWrapper.java
index c4364d9..58a354a 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerWrapper.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerWrapper.java
@@ -43,6 +43,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(api = 28)
+@Deprecated
 class SliceViewManagerWrapper extends SliceViewManagerBase {
     private static final String TAG = "SliceViewManagerWrapper"; // exactly 23
 
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceXml.java b/slice/slice-view/src/main/java/androidx/slice/SliceXml.java
index 1251afb..b09499e 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceXml.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceXml.java
@@ -59,6 +59,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
+@Deprecated
 class SliceXml {
 
     private static final String NAMESPACE = null;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/ActionRow.java b/slice/slice-view/src/main/java/androidx/slice/widget/ActionRow.java
index 15f0ac0..c32f815 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/ActionRow.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/ActionRow.java
@@ -54,6 +54,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class ActionRow extends FrameLayout {
 
     private static final int MAX_ACTIONS = 5;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/DisplayedListItems.java b/slice/slice-view/src/main/java/androidx/slice/widget/DisplayedListItems.java
index 4b5ee95..154d842 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/DisplayedListItems.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/DisplayedListItems.java
@@ -25,6 +25,7 @@
  *
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+@Deprecated
 class DisplayedListItems {
     private final List<SliceContent> mDisplayedItems;
     private final int mHiddenItemCount;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/EventInfo.java b/slice/slice-view/src/main/java/androidx/slice/widget/EventInfo.java
index db5231f..5e315a2 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/EventInfo.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/EventInfo.java
@@ -25,8 +25,13 @@
 
 /**
  * Represents information associated with a logged event on {@link SliceView}.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public class EventInfo {
 
     /**
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/GridContent.java b/slice/slice-view/src/main/java/androidx/slice/widget/GridContent.java
index ee38115..1c8036e 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/GridContent.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/GridContent.java
@@ -55,8 +55,13 @@
 
 /**
  * Extracts information required to present content in a grid format from a slice.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public class GridContent extends SliceContent {
 
     private boolean mAllImages;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/GridRowView.java b/slice/slice-view/src/main/java/androidx/slice/widget/GridRowView.java
index 9bbd5de..b48c559 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/GridRowView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/GridRowView.java
@@ -86,6 +86,12 @@
 import java.util.Iterator;
 import java.util.List;
 
+/**
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
+ */
+@Deprecated
 @RequiresApi(19)
 public class GridRowView extends SliceChildView implements View.OnClickListener,
         View.OnTouchListener {
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/ListContent.java b/slice/slice-view/src/main/java/androidx/slice/widget/ListContent.java
index 8151152..8f28a05 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/ListContent.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/ListContent.java
@@ -54,6 +54,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class ListContent extends SliceContent {
 
     private SliceAction mPrimaryAction;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/LocationBasedViewTracker.java b/slice/slice-view/src/main/java/androidx/slice/widget/LocationBasedViewTracker.java
index 0404633..b7c5639 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/LocationBasedViewTracker.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/LocationBasedViewTracker.java
@@ -34,6 +34,7 @@
  * Utility class to track view based on relative location to the parent.
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
+@Deprecated
 public class LocationBasedViewTracker implements Runnable, View.OnLayoutChangeListener {
 
     private static final SelectionLogic INPUT_FOCUS = new SelectionLogic() {
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/MessageView.java b/slice/slice-view/src/main/java/androidx/slice/widget/MessageView.java
index 8e44e18..d14fbdb 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/MessageView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/MessageView.java
@@ -40,6 +40,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class MessageView extends SliceChildView {
 
     private TextView mDetails;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/RemoteInputView.java b/slice/slice-view/src/main/java/androidx/slice/widget/RemoteInputView.java
index 09392c9..5e89e45 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/RemoteInputView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/RemoteInputView.java
@@ -63,6 +63,7 @@
 @SuppressWarnings("AppCompatCustomView")
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(21)
+@Deprecated
 public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
 
     private static final String TAG = "RemoteInput";
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/RowContent.java b/slice/slice-view/src/main/java/androidx/slice/widget/RowContent.java
index eed88be..dcd403c 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/RowContent.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/RowContent.java
@@ -60,6 +60,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
 @RequiresApi(19)
+@Deprecated
 public class RowContent extends SliceContent {
     private static final String TAG = "RowContent";
 
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/RowStyle.java b/slice/slice-view/src/main/java/androidx/slice/widget/RowStyle.java
index 02416ac..960bfcc 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/RowStyle.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/RowStyle.java
@@ -31,6 +31,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class RowStyle {
     public static final int UNBOUNDED = -1;
 
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/RowStyleFactory.java b/slice/slice-view/src/main/java/androidx/slice/widget/RowStyleFactory.java
index 39e9b74..4ada731 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/RowStyleFactory.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/RowStyleFactory.java
@@ -22,7 +22,12 @@
 
 /**
  * Factory to return different styles for child views of a slice.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
+@Deprecated
 public interface RowStyleFactory {
     /**
      * Returns the style resource to use for this child.
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/RowView.java b/slice/slice-view/src/main/java/androidx/slice/widget/RowView.java
index 34d23e5..84f7712 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/RowView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/RowView.java
@@ -119,8 +119,13 @@
 /**
  * Row item is in small template format and can be used to construct list items for use
  * with {@link TemplateView}.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public class RowView extends SliceChildView implements View.OnClickListener,
         AdapterView.OnItemSelectedListener {
 
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/ShortcutView.java b/slice/slice-view/src/main/java/androidx/slice/widget/ShortcutView.java
index 89ae3d0..5fa1625 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/ShortcutView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/ShortcutView.java
@@ -42,6 +42,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class ShortcutView extends SliceChildView {
 
     private static final String TAG = "ShortcutView";
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceActionView.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceActionView.java
index e5b20d0..0ffdf27 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceActionView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceActionView.java
@@ -56,6 +56,7 @@
 @SuppressWarnings("AppCompatCustomView")
 @RestrictTo(LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class SliceActionView extends FrameLayout implements View.OnClickListener,
         CompoundButton.OnCheckedChangeListener {
     private static final String TAG = "SliceActionView";
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceAdapter.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceAdapter.java
index f8ae73a..4fc45ab 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceAdapter.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceAdapter.java
@@ -47,9 +47,14 @@
 
 /**
  * RecyclerView.Adapter for the Slice components.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @SuppressWarnings("HiddenSuperclass")
 @RequiresApi(19)
+@Deprecated
 public class SliceAdapter extends RecyclerView.Adapter<SliceAdapter.SliceViewHolder>
         implements SliceActionView.SliceActionLoadingListener {
 
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceChildView.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceChildView.java
index b0795bf..9b1b11b 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceChildView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceChildView.java
@@ -35,8 +35,13 @@
 
 /**
  * Base class for children views of {@link SliceView}.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public abstract class SliceChildView extends FrameLayout {
 
     /**
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceContent.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceContent.java
index a56e1e9..0d03a82 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceContent.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceContent.java
@@ -53,8 +53,13 @@
 
 /**
  * Base class representing content that can be displayed.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public class SliceContent {
 
     /**
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceLiveData.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceLiveData.java
index 8fc3a85..ee2ca9e 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceLiveData.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceLiveData.java
@@ -55,8 +55,13 @@
  *
  * @see #fromUri(Context, Uri)
  * @see LiveData
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public final class SliceLiveData {
     private static final String TAG = "SliceLiveData";
 
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceMetrics.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceMetrics.java
index faa5e10..30178d3 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceMetrics.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceMetrics.java
@@ -29,6 +29,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
+@Deprecated
 class SliceMetrics {
 
     public static @Nullable SliceMetrics getInstance(@NonNull Context context, @NonNull Uri uri) {
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceMetricsWrapper.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceMetricsWrapper.java
index adb43d8..fe4abcca 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceMetricsWrapper.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceMetricsWrapper.java
@@ -27,6 +27,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(api = 28)
+@Deprecated
 class SliceMetricsWrapper extends SliceMetrics {
 
     private final android.app.slice.SliceMetrics mSliceMetrics;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceStyle.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceStyle.java
index 2440e67..7d86b57 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceStyle.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceStyle.java
@@ -44,6 +44,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class SliceStyle {
     private int mTintColor = -1;
     private final int mTitleColor;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceView.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceView.java
index 8c6fd44..43cbaf8 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceView.java
@@ -94,8 +94,13 @@
  *
  * @see Slice
  * @see SliceLiveData
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @RequiresApi(19)
+@Deprecated
 public class SliceView extends ViewGroup implements Observer<Slice>, View.OnClickListener {
 
     private static final String TAG = "SliceView";
@@ -104,7 +109,12 @@
      * Implement this interface to be notified of interactions with the slice displayed
      * in this view.
      * @see EventInfo
+     *
+     * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+     * forward. If you are looking for a framework that handles communication across apps,
+     * consider using {@link android.app.appsearch.AppSearchManager}.
      */
+    @Deprecated
     public interface OnSliceActionListener {
         /**
          * Called when an interaction has occurred with an element in this view.
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceViewPolicy.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceViewPolicy.java
index 57dcf89..3c649ac 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceViewPolicy.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceViewPolicy.java
@@ -27,6 +27,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class SliceViewPolicy {
 
     /**
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceViewUtil.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceViewUtil.java
index f4f0499..bb0eef3 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceViewUtil.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceViewUtil.java
@@ -54,6 +54,7 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
+@Deprecated
 public class SliceViewUtil {
 
     /**
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/TemplateView.java b/slice/slice-view/src/main/java/androidx/slice/widget/TemplateView.java
index b26282b..fca31f0 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/TemplateView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/TemplateView.java
@@ -36,9 +36,14 @@
 
 /**
  * Slice template containing all view components.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
  */
 @SuppressWarnings("HiddenSuperclass")
 @RequiresApi(19)
+@Deprecated
 public class TemplateView extends SliceChildView implements
         SliceViewPolicy.PolicyChangeListener {
 
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
index 032cd8a..c4276b5 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
@@ -754,8 +754,12 @@
                     bounds, swipeDirection, segment, speed, getDisplayId()).pause(250);
 
             // Perform the gesture and return early if we reached the end
-            if (mGestureController.performGestureAndWait(
-                    Until.scrollFinished(direction), SCROLL_TIMEOUT, swipe)) {
+            Boolean scrollFinishedResult = mGestureController.performGestureAndWait(
+                    Until.scrollFinished(direction), SCROLL_TIMEOUT, swipe);
+            if (!Boolean.FALSE.equals(scrollFinishedResult)) {
+                if (scrollFinishedResult == null) {
+                    Log.i(TAG, "No scroll event received after scroll.");
+                }
                 return false;
             }
         }
@@ -919,8 +923,12 @@
         // Perform the gesture and return true if we did not reach the end
         Log.d(TAG, String.format("Flinging %s (bounds=%s) at %dpx/s.",
                 direction.name().toLowerCase(), bounds, speed));
-        return !mGestureController.performGestureAndWait(
+        Boolean scrollFinishedResult = mGestureController.performGestureAndWait(
                 Until.scrollFinished(direction), FLING_TIMEOUT, swipe);
+        if (scrollFinishedResult == null) {
+            Log.i(TAG, "No scroll event received after fling.");
+        }
+        return Boolean.FALSE.equals(scrollFinishedResult);
     }
 
     /** Sets this object's text content if it is an editable field. */
diff --git a/transition/transition/build.gradle b/transition/transition/build.gradle
index 165c99b..693fbbd 100644
--- a/transition/transition/build.gradle
+++ b/transition/transition/build.gradle
@@ -10,7 +10,7 @@
     api("androidx.annotation:annotation:1.2.0")
     api("androidx.core:core:1.12.0")
     implementation("androidx.collection:collection:1.1.0")
-    compileOnly("androidx.fragment:fragment:1.2.5")
+    compileOnly(projectOrArtifact(":fragment:fragment"))
     compileOnly("androidx.appcompat:appcompat:1.0.1")
     implementation("androidx.dynamicanimation:dynamicanimation:1.0.0")
 
diff --git a/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSeekingTest.kt b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSeekingTest.kt
new file mode 100644
index 0000000..868a511
--- /dev/null
+++ b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSeekingTest.kt
@@ -0,0 +1,446 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.transition
+
+import android.os.Build
+import android.window.BackEvent
+import androidx.activity.BackEventCompat
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.testutils.waitForExecution
+import androidx.transition.test.R
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class FragmentTransitionSeekingTest {
+
+    @Suppress("DEPRECATION")
+    @get:Rule
+    val activityRule = androidx.test.rule.ActivityTestRule(
+        FragmentTransitionTestActivity::class.java
+    )
+
+    @Test
+    fun replaceOperationWithTransitionsThenGestureBack() {
+        val fm1 = activityRule.activity.supportFragmentManager
+
+        var startedEnter = false
+        val fragment1 = TransitionFragment(R.layout.scene1)
+        fragment1.setReenterTransition(Fade().apply {
+            duration = 300
+            addListener(object : TransitionListenerAdapter() {
+                override fun onTransitionStart(transition: Transition) {
+                    startedEnter = true
+                }
+            })
+        })
+
+        fm1.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment1, "1")
+            .setReorderingAllowed(true)
+            .addToBackStack(null)
+            .commit()
+        activityRule.waitForExecution()
+
+        val startedExitCountDownLatch = CountDownLatch(1)
+        val fragment2 = TransitionFragment()
+        fragment2.setReturnTransition(Fade().apply {
+            duration = 300
+            addListener(object : TransitionListenerAdapter() {
+                override fun onTransitionStart(transition: Transition) {
+                    startedExitCountDownLatch.countDown()
+                }
+            })
+        })
+
+        fm1.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment2, "2")
+            .setReorderingAllowed(true)
+            .addToBackStack(null)
+            .commit()
+        activityRule.executePendingTransactions()
+
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+
+        val dispatcher = activityRule.activity.onBackPressedDispatcher
+        activityRule.runOnUiThread {
+            dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT))
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        activityRule.runOnUiThread {
+            dispatcher.dispatchOnBackProgressed(
+                BackEventCompat(0.2F, 0.2F, 0.2F, BackEvent.EDGE_LEFT)
+            )
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        assertThat(startedEnter).isTrue()
+        assertThat(startedExitCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+        activityRule.runOnUiThread {
+            dispatcher.onBackPressed()
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        fragment1.waitForTransition()
+
+        assertThat(fragment2.isAdded).isFalse()
+        assertThat(fm1.findFragmentByTag("2"))
+            .isEqualTo(null)
+
+        // Make sure the original fragment was correctly readded to the container
+        assertThat(fragment1.requireView().parent).isNotNull()
+    }
+
+    @Test
+    fun replaceOperationWithTransitionsThenBackCancelled() {
+        val fm1 = activityRule.activity.supportFragmentManager
+
+        var startedEnter = false
+        val fragment1 = TransitionFragment(R.layout.scene1)
+        fragment1.setReenterTransition(Fade().apply {
+            duration = 300
+            addListener(object : TransitionListenerAdapter() {
+                override fun onTransitionStart(transition: Transition) {
+                    startedEnter = true
+                }
+            })
+        })
+
+        fm1.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment1, "1")
+            .setReorderingAllowed(true)
+            .addToBackStack(null)
+            .commit()
+        activityRule.waitForExecution()
+
+        val startedExitCountDownLatch = CountDownLatch(1)
+        val fragment2 = TransitionFragment()
+        fragment2.setReturnTransition(Fade().apply {
+            duration = 300
+            addListener(object : TransitionListenerAdapter() {
+                override fun onTransitionStart(transition: Transition) {
+                    startedExitCountDownLatch.countDown()
+                }
+            })
+        })
+
+        fm1.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment2, "2")
+            .setReorderingAllowed(true)
+            .addToBackStack(null)
+            .commit()
+        activityRule.executePendingTransactions()
+
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+
+        val dispatcher = activityRule.activity.onBackPressedDispatcher
+        activityRule.runOnUiThread {
+            dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT))
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        activityRule.runOnUiThread {
+            dispatcher.dispatchOnBackProgressed(
+                BackEventCompat(0.2F, 0.2F, 0.2F, BackEvent.EDGE_LEFT)
+            )
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        assertThat(startedEnter).isTrue()
+        assertThat(startedExitCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+        activityRule.runOnUiThread {
+            dispatcher.dispatchOnBackCancelled()
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        fragment1.waitForTransition()
+
+        assertThat(fragment2.isAdded).isTrue()
+        assertThat(fm1.findFragmentByTag("2")).isEqualTo(fragment2)
+
+        // Make sure the original fragment was correctly readded to the container
+        assertThat(fragment2.requireView()).isNotNull()
+    }
+
+    @Test
+    fun replaceOperationWithTransitionsThenGestureBackTwice() {
+        val fm1 = activityRule.activity.supportFragmentManager
+
+        var startedEnter = false
+        val fragment1 = TransitionFragment(R.layout.scene1)
+        fragment1.setReenterTransition(Fade().apply {
+            duration = 300
+            addListener(object : TransitionListenerAdapter() {
+                override fun onTransitionStart(transition: Transition) {
+                    startedEnter = true
+                }
+            })
+        })
+
+        fm1.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment1, "1")
+            .setReorderingAllowed(true)
+            .addToBackStack(null)
+            .commit()
+        activityRule.waitForExecution()
+
+        val fragment2startedExitCountDownLatch = CountDownLatch(1)
+        val fragment2 = TransitionFragment()
+        fragment2.setReenterTransition(Fade().apply { duration = 300 })
+        fragment2.setReturnTransition(Fade().apply {
+            duration = 300
+            addListener(object : TransitionListenerAdapter() {
+                override fun onTransitionStart(transition: Transition) {
+                    fragment2startedExitCountDownLatch.countDown()
+                }
+            })
+        })
+
+        fm1.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment2, "2")
+            .setReorderingAllowed(true)
+            .addToBackStack(null)
+            .commit()
+        activityRule.executePendingTransactions()
+
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+
+        val fragment3startedExitCountDownLatch = CountDownLatch(1)
+        val fragment3 = TransitionFragment()
+        fragment3.setReturnTransition(Fade().apply {
+            duration = 300
+            addListener(object : TransitionListenerAdapter() {
+                override fun onTransitionStart(transition: Transition) {
+                    fragment3startedExitCountDownLatch.countDown()
+                }
+            })
+        })
+
+        fm1.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment3, "3")
+            .setReorderingAllowed(true)
+            .addToBackStack(null)
+            .commit()
+        activityRule.executePendingTransactions()
+
+        assertThat(fragment3.startTransitionCountDownLatch.await(1000, TimeUnit.MILLISECONDS))
+            .isTrue()
+        // We need to wait for the exit animation to end
+        assertThat(fragment2.endTransitionCountDownLatch.await(1000, TimeUnit.MILLISECONDS))
+            .isTrue()
+
+        val dispatcher = activityRule.activity.onBackPressedDispatcher
+        activityRule.runOnUiThread {
+            dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT))
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        activityRule.runOnUiThread {
+            dispatcher.dispatchOnBackProgressed(
+                BackEventCompat(0.2F, 0.2F, 0.2F, BackEvent.EDGE_LEFT)
+            )
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        assertThat(fragment3startedExitCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+        activityRule.runOnUiThread {
+            dispatcher.onBackPressed()
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        fragment2.waitForTransition()
+        fragment3.waitForTransition()
+
+        assertThat(fragment3.isAdded).isFalse()
+        assertThat(fm1.findFragmentByTag("3")).isEqualTo(null)
+
+        // Make sure the original fragment was correctly readded to the container
+        assertThat(fragment2.requireView().parent).isNotNull()
+
+        val fragment2ResumedLatch = CountDownLatch(1)
+        activityRule.runOnUiThread {
+            fragment2.lifecycle.addObserver(
+                object : LifecycleEventObserver {
+                    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+                        if (event.targetState == Lifecycle.State.RESUMED) {
+                            fragment2ResumedLatch.countDown()
+                        }
+                    }
+                }
+            )
+        }
+
+        assertThat(fragment2ResumedLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+        activityRule.runOnUiThread {
+            dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT))
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        activityRule.runOnUiThread {
+            dispatcher.dispatchOnBackProgressed(
+                BackEventCompat(0.2F, 0.2F, 0.2F, BackEvent.EDGE_LEFT)
+            )
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        assertThat(startedEnter).isTrue()
+        assertThat(fragment2startedExitCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+        activityRule.runOnUiThread {
+            dispatcher.onBackPressed()
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        fragment1.waitForTransition()
+
+        assertThat(fragment2.isAdded).isFalse()
+        assertThat(fm1.findFragmentByTag("2")).isEqualTo(null)
+
+        // Make sure the original fragment was correctly readded to the container
+        assertThat(fragment1.requireView().parent).isNotNull()
+    }
+
+    @Test
+    fun replaceOperationWithTransitionsThenOnBackPressedTwice() {
+        val fm1 = activityRule.activity.supportFragmentManager
+
+        var startedEnter = false
+        val fragment1 = TransitionFragment(R.layout.scene1)
+        fragment1.setReenterTransition(Fade().apply {
+            duration = 300
+            addListener(object : TransitionListenerAdapter() {
+                override fun onTransitionStart(transition: Transition) {
+                    startedEnter = true
+                }
+            })
+        })
+
+        fm1.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment1, "1")
+            .setReorderingAllowed(true)
+            .addToBackStack(null)
+            .commit()
+        activityRule.waitForExecution()
+
+        val fragment2startedExitCountDownLatch = CountDownLatch(1)
+        val fragment2 = TransitionFragment()
+        fragment2.setReturnTransition(Fade().apply {
+            duration = 300
+            addListener(object : TransitionListenerAdapter() {
+                override fun onTransitionStart(transition: Transition) {
+                    fragment2startedExitCountDownLatch.countDown()
+                }
+            })
+        })
+
+        fm1.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment2, "2")
+            .setReorderingAllowed(true)
+            .addToBackStack(null)
+            .commit()
+        activityRule.executePendingTransactions()
+
+        fragment1.waitForTransition()
+        fragment2.waitForTransition()
+
+        val fragment3startedExitCountDownLatch = CountDownLatch(1)
+        val fragment3 = TransitionFragment()
+        fragment3.setReturnTransition(Fade().apply {
+            duration = 300
+            addListener(object : TransitionListenerAdapter() {
+                override fun onTransitionStart(transition: Transition) {
+                    fragment3startedExitCountDownLatch.countDown()
+                }
+            })
+        })
+
+        fm1.beginTransaction()
+            .replace(R.id.fragmentContainer, fragment3, "3")
+            .setReorderingAllowed(true)
+            .addToBackStack(null)
+            .commit()
+        activityRule.executePendingTransactions()
+
+        assertThat(fragment3.startTransitionCountDownLatch.await(1000, TimeUnit.MILLISECONDS))
+            .isTrue()
+        // We need to wait for the exit animation to end
+        assertThat(fragment2.endTransitionCountDownLatch.await(1000, TimeUnit.MILLISECONDS))
+            .isTrue()
+
+        val dispatcher = activityRule.activity.onBackPressedDispatcher
+        activityRule.runOnUiThread {
+            dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT))
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        activityRule.runOnUiThread {
+            dispatcher.onBackPressed()
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        assertThat(fragment3startedExitCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+        fragment2.waitForTransition()
+        fragment3.waitForTransition()
+
+        assertThat(fragment3.isAdded).isFalse()
+        assertThat(fm1.findFragmentByTag("3")).isEqualTo(null)
+
+        // Make sure the original fragment was correctly readded to the container
+        assertThat(fragment2.requireView().parent).isNotNull()
+
+        activityRule.runOnUiThread {
+            dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT))
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        activityRule.runOnUiThread {
+            dispatcher.onBackPressed()
+        }
+        activityRule.executePendingTransactions(fm1)
+
+        assertThat(startedEnter).isTrue()
+        assertThat(fragment2startedExitCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+        fragment1.waitForTransition()
+
+        assertThat(fragment2.isAdded).isFalse()
+        assertThat(fm1.findFragmentByTag("2")).isEqualTo(null)
+
+        // Make sure the original fragment was correctly readded to the container
+        assertThat(fragment1.requireView().parent).isNotNull()
+    }
+}
diff --git a/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java b/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
index 217c891..6a0d6c9 100644
--- a/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
+++ b/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
@@ -20,6 +20,7 @@
 
 import android.annotation.SuppressLint;
 import android.graphics.Rect;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -228,6 +229,54 @@
     }
 
     @Override
+    public boolean isSeekingSupported(@NonNull Object transition) {
+        boolean supported = ((Transition) transition).isSeekingSupported();
+        if (!supported) {
+            Log.v("FragmentManager",
+                    "Predictive back not available for AndroidX Transition "
+                            + transition + ". Please enable seeking support for the designated "
+                            + "transition by overriding isSeekingSupported().");
+        }
+        return supported;
+    }
+
+    @Override
+    @Nullable
+    public Object controlDelayedTransition(@NonNull ViewGroup sceneRoot,
+            @NonNull Object transition) {
+        return TransitionManager.controlDelayedTransition(sceneRoot, (Transition) transition);
+    }
+
+    @Override
+    public void setCurrentPlayTime(@NonNull Object transitionController, float progress) {
+        TransitionSeekController controller = (TransitionSeekController) transitionController;
+        if (controller.isReady()) {
+            long time = (long) (progress * controller.getDurationMillis());
+            // We cannot let the time get to 0 or the totalDuration to avoid
+            // completing the operation accidentally.
+            if (time == 0L) {
+                time = 1L;
+            }
+            if (time == controller.getDurationMillis()) {
+                time = controller.getDurationMillis() - 1;
+            }
+            controller.setCurrentPlayTimeMillis(time);
+        }
+    }
+
+    @Override
+    public void animateToEnd(@NonNull Object transitionController) {
+        TransitionSeekController controller = (TransitionSeekController) transitionController;
+        controller.animateToEnd();
+    }
+
+    @Override
+    public void animateToStart(@NonNull Object transitionController) {
+        TransitionSeekController controller = (TransitionSeekController) transitionController;
+        controller.animateToStart();
+    }
+
+    @Override
     public void scheduleRemoveTargets(final @NonNull Object overallTransitionObj,
             final @Nullable Object enterTransition, final @Nullable ArrayList<View> enteringViews,
             final @Nullable Object exitTransition, final @Nullable ArrayList<View> exitingViews,
@@ -269,11 +318,23 @@
     public void setListenerForTransitionEnd(@NonNull final Fragment outFragment,
             @NonNull final Object transition, @NonNull final CancellationSignal signal,
             @NonNull final Runnable transitionCompleteRunnable) {
+        setListenerForTransitionEnd(outFragment, transition, signal,
+                null, transitionCompleteRunnable);
+    }
+
+    @Override
+    public void setListenerForTransitionEnd(@NonNull Fragment outFragment,
+            @NonNull Object transition, @NonNull CancellationSignal signal,
+            @Nullable Runnable cancelRunnable, @NonNull Runnable transitionCompleteRunnable) {
         final Transition realTransition = ((Transition) transition);
         signal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
             @Override
             public void onCancel() {
-                realTransition.cancel();
+                if (cancelRunnable == null) {
+                    realTransition.cancel();
+                } else {
+                    cancelRunnable.run();
+                }
             }
         });
         realTransition.addListener(new Transition.TransitionListener() {
diff --git a/tv/integration-tests/playground/build.gradle b/tv/integration-tests/playground/build.gradle
index 4970492..0842d00 100644
--- a/tv/integration-tests/playground/build.gradle
+++ b/tv/integration-tests/playground/build.gradle
@@ -32,6 +32,8 @@
     implementation(project(":compose:material3:material3"))
     implementation(project(":navigation:navigation-compose"))
     implementation(project(":profileinstaller:profileinstaller"))
+    implementation(project(":compose:material:material-icons-core"))
+    implementation(project(":compose:material:material-icons-extended"))
 
     implementation(project(":tv:tv-foundation"))
     implementation(project(":tv:tv-material"))
diff --git a/tv/integration-tests/playground/src/main/baseline-prof.txt b/tv/integration-tests/playground/src/main/baseline-prof.txt
new file mode 100644
index 0000000..970cd8b
--- /dev/null
+++ b/tv/integration-tests/playground/src/main/baseline-prof.txt
@@ -0,0 +1,5623 @@
+HPLandroidx/collection/ArrayMap$EntrySet;-><init>(Ljava/util/Map;I)V
+HPLandroidx/compose/foundation/layout/SizeElement;->equals(Ljava/lang/Object;)Z
+HPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->unregisterComposer$runtime_release(Landroidx/compose/runtime/Composer;)V
+HPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->unregisterComposition$runtime_release(Landroidx/compose/runtime/CompositionImpl;)V
+HPLandroidx/compose/runtime/ComposerImpl;->dispose$runtime_release()V
+HPLandroidx/compose/runtime/CompositionImpl;->dispose()V
+HPLandroidx/compose/runtime/Recomposer;->unregisterComposition$runtime_release(Landroidx/compose/runtime/CompositionImpl;)V
+HPLandroidx/compose/runtime/SlotWriter$groupSlots$1;-><init>(IILandroidx/compose/runtime/SlotWriter;)V
+HPLandroidx/compose/runtime/SlotWriter$groupSlots$1;->hasNext()Z
+HPLandroidx/compose/runtime/SlotWriter$groupSlots$1;->next()Ljava/lang/Object;
+HPLandroidx/compose/runtime/SlotWriter;->removeGroup()Z
+HPLandroidx/compose/runtime/SlotWriter;->removeGroups(II)Z
+HPLandroidx/compose/runtime/SlotWriter;->removeSlots(III)V
+HPLandroidx/compose/runtime/SlotWriter;->skipGroup()I
+HPLandroidx/compose/runtime/internal/ComposableLambdaImpl;->trackRead(Landroidx/compose/runtime/Composer;)V
+HPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->getCurrentSnapshot()Landroidx/compose/runtime/snapshots/MutableSnapshot;
+HPLandroidx/compose/ui/focus/FocusOwnerImpl$moveFocus$foundNextItem$1;->invoke(Landroidx/compose/ui/layout/BeyondBoundsLayout$BeyondBoundsScope;)Ljava/lang/Boolean;
+HPLandroidx/compose/ui/layout/Placeable$PlacementScope;->placeWithLayer-aW-9-wM$default(Landroidx/compose/ui/layout/Placeable$PlacementScope;Landroidx/compose/ui/layout/Placeable;J)V
+HPLandroidx/compose/ui/modifier/BackwardsCompatLocalMap;->get$ui_release(Landroidx/compose/ui/modifier/ProvidableModifierLocal;)Ljava/lang/Object;
+HPLandroidx/compose/ui/node/LayoutNode;->detach$ui_release()V
+HPLandroidx/compose/ui/node/LayoutNode;->onChildRemoved(Landroidx/compose/ui/node/LayoutNode;)V
+HPLandroidx/compose/ui/node/LayoutNode;->removeAll$ui_release()V
+HPLandroidx/compose/ui/node/LayoutNode;->removeAt$ui_release(II)V
+HPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->requestRemeasure(Landroidx/compose/ui/node/LayoutNode;Z)Z
+HPLandroidx/compose/ui/node/UiApplier;->clear()V
+HPLandroidx/compose/ui/platform/AndroidComposeView;->getFocusedRect(Landroid/graphics/Rect;)V
+HPLandroidx/tv/foundation/lazy/layout/LazyLayoutBeyondBoundsInfo$Interval;-><init>(II)V
+HPLandroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal$layout$2;-><init>(Landroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal;Lkotlin/jvm/internal/Ref$ObjectRef;I)V
+HPLandroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal$layout$2;->getHasMoreContent()Z
+HPLandroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal;->hasMoreContent-FR3nfPY(Landroidx/tv/foundation/lazy/layout/LazyLayoutBeyondBoundsInfo$Interval;I)Z
+HPLandroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal;->isForward-4vf7U8o(I)Z
+HPLandroidx/tv/foundation/lazy/list/LazyListBeyondBoundsState;->getFirstPlacedIndex()I
+HPLandroidx/tv/foundation/lazy/list/LazyListBeyondBoundsState;->getHasVisibleItems()Z
+HPLandroidx/tv/foundation/lazy/list/LazyListBeyondBoundsState;->getItemCount()I
+HPLandroidx/tv/foundation/lazy/list/LazyListBeyondBoundsState;->getLastPlacedIndex()I
+HPLandroidx/tv/foundation/lazy/list/LazyListBeyondBoundsState;->remeasure()V
+HPLcom/example/tvcomposebasedtests/JankStatsAggregator;->issueJankReport(Ljava/lang/String;)V
+HPLcom/google/gson/Gson$3;->write(Lcom/google/gson/stream/JsonWriter;Ljava/lang/Boolean;)V
+HPLcom/google/gson/Gson$3;->write(Lcom/google/gson/stream/JsonWriter;Ljava/lang/Number;)V
+HPLcom/google/gson/Gson$3;->write(Lcom/google/gson/stream/JsonWriter;Ljava/lang/Object;)V
+HPLcom/google/gson/Gson;->getAdapter(Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+HPLcom/google/gson/JsonArray;-><init>()V
+HPLcom/google/gson/JsonArray;->iterator()Ljava/util/Iterator;
+HPLcom/google/gson/JsonObject;-><init>()V
+HPLcom/google/gson/JsonPrimitive;-><init>(Ljava/lang/Boolean;)V
+HPLcom/google/gson/JsonPrimitive;-><init>(Ljava/lang/Number;)V
+HPLcom/google/gson/JsonPrimitive;->getAsNumber()Ljava/lang/Number;
+HPLcom/google/gson/internal/LinkedTreeMap$LinkedTreeMapIterator;-><init>(Lcom/google/gson/internal/LinkedTreeMap;)V
+HPLcom/google/gson/internal/LinkedTreeMap$LinkedTreeMapIterator;->hasNext()Z
+HPLcom/google/gson/internal/LinkedTreeMap$Node;-><init>(Z)V
+HPLcom/google/gson/internal/LinkedTreeMap$Node;-><init>(ZLcom/google/gson/internal/LinkedTreeMap$Node;Ljava/lang/Object;Lcom/google/gson/internal/LinkedTreeMap$Node;Lcom/google/gson/internal/LinkedTreeMap$Node;)V
+HPLcom/google/gson/internal/LinkedTreeMap$Node;->getValue()Ljava/lang/Object;
+HPLcom/google/gson/internal/LinkedTreeMap;-><init>(Z)V
+HPLcom/google/gson/internal/LinkedTreeMap;->entrySet()Ljava/util/Set;
+HPLcom/google/gson/internal/LinkedTreeMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HPLcom/google/gson/internal/LinkedTreeMap;->rebalance(Lcom/google/gson/internal/LinkedTreeMap$Node;Z)V
+HPLcom/google/gson/internal/LinkedTreeMap;->replaceInParent(Lcom/google/gson/internal/LinkedTreeMap$Node;Lcom/google/gson/internal/LinkedTreeMap$Node;)V
+HPLcom/google/gson/internal/LinkedTreeMap;->rotateLeft(Lcom/google/gson/internal/LinkedTreeMap$Node;)V
+HPLcom/google/gson/internal/LinkedTreeMap;->rotateRight(Lcom/google/gson/internal/LinkedTreeMap$Node;)V
+HPLcom/google/gson/internal/bind/JsonTreeWriter;-><init>()V
+HPLcom/google/gson/internal/bind/JsonTreeWriter;->beginArray()V
+HPLcom/google/gson/internal/bind/JsonTreeWriter;->endArray()V
+HPLcom/google/gson/internal/bind/JsonTreeWriter;->endObject()V
+HPLcom/google/gson/internal/bind/JsonTreeWriter;->get()Lcom/google/gson/JsonElement;
+HPLcom/google/gson/internal/bind/JsonTreeWriter;->name(Ljava/lang/String;)V
+HPLcom/google/gson/internal/bind/JsonTreeWriter;->peek()Lcom/google/gson/JsonElement;
+HPLcom/google/gson/internal/bind/JsonTreeWriter;->put(Lcom/google/gson/JsonElement;)V
+HPLcom/google/gson/internal/bind/JsonTreeWriter;->value(J)V
+HPLcom/google/gson/internal/bind/ReflectiveTypeAdapterFactory$1;->write(Lcom/google/gson/stream/JsonWriter;Ljava/lang/Object;)V
+HPLcom/google/gson/internal/bind/ReflectiveTypeAdapterFactory$Adapter;->write(Lcom/google/gson/stream/JsonWriter;Ljava/lang/Object;)V
+HPLcom/google/gson/internal/bind/TypeAdapters$34$1;->write(Lcom/google/gson/stream/JsonWriter;Ljava/lang/Object;)V
+HPLcom/google/gson/internal/bind/TypeAdapters$EnumTypeAdapter;-><init>(Lcom/google/gson/Gson;Lcom/google/gson/TypeAdapter;Ljava/lang/reflect/Type;)V
+HPLcom/google/gson/reflect/TypeToken;-><init>(Ljava/lang/reflect/Type;)V
+HPLcom/google/gson/reflect/TypeToken;->equals(Ljava/lang/Object;)Z
+HPLcom/google/gson/reflect/TypeToken;->hashCode()I
+HPLcom/google/gson/stream/JsonWriter;-><init>(Ljava/io/Writer;)V
+HPLcom/google/gson/stream/JsonWriter;->beforeValue()V
+HPLcom/google/gson/stream/JsonWriter;->beginArray()V
+HPLcom/google/gson/stream/JsonWriter;->beginObject()V
+HPLcom/google/gson/stream/JsonWriter;->close(IIC)V
+HPLcom/google/gson/stream/JsonWriter;->endObject()V
+HPLcom/google/gson/stream/JsonWriter;->peek()I
+HPLcom/google/gson/stream/JsonWriter;->string(Ljava/lang/String;)V
+HPLcom/google/gson/stream/JsonWriter;->value(Ljava/lang/Number;)V
+HPLcom/google/gson/stream/JsonWriter;->value(Z)V
+HPLcom/google/gson/stream/JsonWriter;->writeDeferredName()V
+HPLkotlin/ResultKt;->canonicalize(Ljava/lang/reflect/Type;)Ljava/lang/reflect/Type;
+HPLkotlin/ResultKt;->getRawType(Ljava/lang/reflect/Type;)Ljava/lang/Class;
+HPLkotlin/TuplesKt;->fastFilter(Ljava/util/ArrayList;Lkotlin/jvm/functions/Function1;)Ljava/util/ArrayList;
+HPLkotlin/TuplesKt;->removeCurrentGroup(Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HPLkotlin/collections/ArrayDeque;->add(ILjava/lang/Object;)V
+HPLkotlin/collections/CollectionsKt___CollectionsKt;->first(Ljava/util/List;)Ljava/lang/Object;
+HSPL_COROUTINE/ArtificialStackFrames;-><init>(I)V
+HSPL_COROUTINE/ArtificialStackFrames;-><init>(II)V
+HSPLandroidx/activity/ComponentActivity$$ExternalSyntheticLambda0;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/activity/ComponentActivity$$ExternalSyntheticLambda1;-><init>(Landroidx/activity/ComponentActivity;)V
+HSPLandroidx/activity/ComponentActivity$$ExternalSyntheticLambda2;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/activity/ComponentActivity$$ExternalSyntheticLambda3;-><init>(Landroidx/activity/ComponentActivity;)V
+HSPLandroidx/activity/ComponentActivity$$ExternalSyntheticLambda3;->onContextAvailable()V
+HSPLandroidx/activity/ComponentActivity$1;-><init>(Landroid/view/KeyEvent$Callback;I)V
+HSPLandroidx/activity/ComponentActivity$2;-><init>()V
+HSPLandroidx/activity/ComponentActivity$3;-><init>(Landroidx/activity/ComponentActivity;)V
+HSPLandroidx/activity/ComponentActivity$3;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/activity/ComponentActivity$4;-><init>(Landroidx/activity/ComponentActivity;)V
+HSPLandroidx/activity/ComponentActivity$4;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/activity/ComponentActivity$5;-><init>(Landroidx/activity/ComponentActivity;)V
+HSPLandroidx/activity/ComponentActivity$5;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/activity/ComponentActivity$ReportFullyDrawnExecutorApi16Impl;-><init>(Landroidx/activity/ComponentActivity;)V
+HSPLandroidx/activity/ComponentActivity$ReportFullyDrawnExecutorApi16Impl;->onDraw()V
+HSPLandroidx/activity/ComponentActivity$ReportFullyDrawnExecutorApi16Impl;->viewCreated(Landroid/view/View;)V
+HSPLandroidx/activity/ComponentActivity;-><init>()V
+HSPLandroidx/activity/ComponentActivity;->ensureViewModelStore()V
+HSPLandroidx/activity/ComponentActivity;->getLifecycle()Landroidx/lifecycle/LifecycleRegistry;
+HSPLandroidx/activity/ComponentActivity;->getViewModelStore()Landroidx/lifecycle/ViewModelStore;
+HSPLandroidx/activity/ComponentActivity;->initViewTreeOwners()V
+HSPLandroidx/activity/ComponentActivity;->onCreate(Landroid/os/Bundle;)V
+HSPLandroidx/activity/ComponentActivity;->setContentView(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V
+HSPLandroidx/activity/FullyDrawnReporter;-><init>(Landroidx/activity/ComponentActivity$ReportFullyDrawnExecutorApi16Impl;Landroidx/activity/ComponentActivity$$ExternalSyntheticLambda1;)V
+HSPLandroidx/activity/OnBackPressedDispatcher;-><init>(Landroidx/activity/ComponentActivity$1;)V
+HSPLandroidx/activity/compose/ComponentActivityKt;-><clinit>()V
+HSPLandroidx/activity/compose/ComponentActivityKt;->setContent$default(Landroidx/activity/ComponentActivity;Landroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLandroidx/activity/contextaware/ContextAwareHelper;-><init>()V
+HSPLandroidx/activity/result/ActivityResult$1;-><init>(I)V
+HSPLandroidx/arch/core/executor/ArchTaskExecutor;-><init>()V
+HSPLandroidx/arch/core/executor/ArchTaskExecutor;->getInstance()Landroidx/arch/core/executor/ArchTaskExecutor;
+HSPLandroidx/arch/core/executor/DefaultTaskExecutor$1;-><init>()V
+HSPLandroidx/arch/core/executor/DefaultTaskExecutor;-><init>()V
+HSPLandroidx/arch/core/internal/FastSafeIterableMap;-><init>()V
+HSPLandroidx/arch/core/internal/FastSafeIterableMap;->get(Ljava/lang/Object;)Landroidx/arch/core/internal/SafeIterableMap$Entry;
+HSPLandroidx/arch/core/internal/FastSafeIterableMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/arch/core/internal/SafeIterableMap$AscendingIterator;-><init>(Landroidx/arch/core/internal/SafeIterableMap$Entry;Landroidx/arch/core/internal/SafeIterableMap$Entry;I)V
+HSPLandroidx/arch/core/internal/SafeIterableMap$Entry;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
+HSPLandroidx/arch/core/internal/SafeIterableMap$Entry;->getKey()Ljava/lang/Object;
+HSPLandroidx/arch/core/internal/SafeIterableMap$Entry;->getValue()Ljava/lang/Object;
+HSPLandroidx/arch/core/internal/SafeIterableMap$IteratorWithAdditions;-><init>(Landroidx/arch/core/internal/SafeIterableMap;)V
+HSPLandroidx/arch/core/internal/SafeIterableMap$IteratorWithAdditions;->hasNext()Z
+HSPLandroidx/arch/core/internal/SafeIterableMap$IteratorWithAdditions;->next()Ljava/lang/Object;
+HSPLandroidx/arch/core/internal/SafeIterableMap$IteratorWithAdditions;->supportRemove(Landroidx/arch/core/internal/SafeIterableMap$Entry;)V
+HSPLandroidx/arch/core/internal/SafeIterableMap$ListIterator;-><init>(Landroidx/arch/core/internal/SafeIterableMap$Entry;Landroidx/arch/core/internal/SafeIterableMap$Entry;)V
+HSPLandroidx/arch/core/internal/SafeIterableMap$ListIterator;->hasNext()Z
+HSPLandroidx/arch/core/internal/SafeIterableMap;-><init>()V
+HSPLandroidx/arch/core/internal/SafeIterableMap;->get(Ljava/lang/Object;)Landroidx/arch/core/internal/SafeIterableMap$Entry;
+HSPLandroidx/arch/core/internal/SafeIterableMap;->iterator()Ljava/util/Iterator;
+HSPLandroidx/arch/core/internal/SafeIterableMap;->remove(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/collection/ArraySet;-><clinit>()V
+HSPLandroidx/collection/ArraySet;-><init>()V
+HSPLandroidx/collection/ArraySet;->add(Ljava/lang/Object;)Z
+HSPLandroidx/collection/ArraySet;->allocArrays(I)V
+HSPLandroidx/collection/ArraySet;->clear()V
+HSPLandroidx/collection/ArraySet;->freeArrays([I[Ljava/lang/Object;I)V
+HSPLandroidx/collection/ArraySet;->indexOf(ILjava/lang/Object;)I
+HSPLandroidx/collection/ArraySet;->toArray()[Ljava/lang/Object;
+HSPLandroidx/collection/LongSparseArray;-><clinit>()V
+HSPLandroidx/collection/LongSparseArray;-><init>(I)V
+HSPLandroidx/collection/SimpleArrayMap;-><init>()V
+HSPLandroidx/collection/SparseArrayCompat;-><clinit>()V
+HSPLandroidx/collection/SparseArrayCompat;-><init>()V
+HSPLandroidx/compose/animation/FlingCalculator;-><init>(FLandroidx/compose/ui/unit/Density;)V
+HSPLandroidx/compose/animation/FlingCalculatorKt;-><clinit>()V
+HSPLandroidx/compose/animation/SingleValueAnimationKt;-><clinit>()V
+HSPLandroidx/compose/animation/SingleValueAnimationKt;->animateColorAsState-euL9pac(JLjava/lang/String;Landroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State;
+HSPLandroidx/compose/animation/SplineBasedFloatDecayAnimationSpec;-><init>(Landroidx/compose/ui/unit/Density;)V
+HSPLandroidx/compose/animation/SplineBasedFloatDecayAnimationSpec_androidKt;-><clinit>()V
+HSPLandroidx/compose/animation/core/Animatable$runAnimation$2;-><init>(Landroidx/compose/animation/core/Animatable;Ljava/lang/Object;Landroidx/compose/animation/core/Animation;JLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/animation/core/Animatable$runAnimation$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/Animatable$runAnimation$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/Animatable;-><init>(Ljava/lang/Object;Landroidx/compose/animation/core/TwoWayConverterImpl;Ljava/lang/Object;)V
+HSPLandroidx/compose/animation/core/Animatable;->animateTo$default(Landroidx/compose/animation/core/Animatable;Ljava/lang/Object;Landroidx/compose/animation/core/AnimationSpec;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/Animatable;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$2;-><init>(Lkotlinx/coroutines/channels/Channel;Ljava/lang/Object;)V
+HSPLandroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$2;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$3$1;-><init>(Ljava/lang/Object;Landroidx/compose/animation/core/Animatable;Landroidx/compose/runtime/State;Landroidx/compose/runtime/State;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$3$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$3$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$3;-><init>(Lkotlinx/coroutines/channels/Channel;Landroidx/compose/animation/core/Animatable;Landroidx/compose/runtime/State;Landroidx/compose/runtime/State;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$3;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$3;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/AnimateAsStateKt;-><clinit>()V
+HSPLandroidx/compose/animation/core/AnimateAsStateKt;->animateDpAsState-AjpBEmI(FLjava/lang/String;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/State;
+HSPLandroidx/compose/animation/core/AnimateAsStateKt;->animateFloatAsState(FLandroidx/compose/animation/core/TweenSpec;Ljava/lang/String;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/State;
+HSPLandroidx/compose/animation/core/AnimateAsStateKt;->animateValueAsState(Ljava/lang/Object;Landroidx/compose/animation/core/TwoWayConverterImpl;Landroidx/compose/animation/core/AnimationSpec;Ljava/lang/Float;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/State;
+HSPLandroidx/compose/animation/core/Animation;->isFinishedFromNanos(J)Z
+HSPLandroidx/compose/animation/core/AnimationEndReason$EnumUnboxingLocalUtility;-><clinit>()V
+HSPLandroidx/compose/animation/core/AnimationEndReason$EnumUnboxingLocalUtility;->compareTo(II)I
+HSPLandroidx/compose/animation/core/AnimationEndReason$EnumUnboxingLocalUtility;->ordinal(I)I
+HSPLandroidx/compose/animation/core/AnimationEndReason$EnumUnboxingLocalUtility;->values(I)[I
+HSPLandroidx/compose/animation/core/AnimationResult;-><init>(Landroidx/compose/animation/core/AnimationState;I)V
+HSPLandroidx/compose/animation/core/AnimationScope;-><init>(Ljava/lang/Object;Landroidx/compose/animation/core/TwoWayConverterImpl;Landroidx/compose/animation/core/AnimationVector;JLjava/lang/Object;JLandroidx/compose/animation/core/SuspendAnimationKt$animate$7;)V
+HSPLandroidx/compose/animation/core/AnimationScope;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/AnimationState;-><init>(Landroidx/compose/animation/core/TwoWayConverterImpl;Ljava/lang/Object;Landroidx/compose/animation/core/AnimationVector;I)V
+HSPLandroidx/compose/animation/core/AnimationState;-><init>(Landroidx/compose/animation/core/TwoWayConverterImpl;Ljava/lang/Object;Landroidx/compose/animation/core/AnimationVector;JJZ)V
+HSPLandroidx/compose/animation/core/AnimationState;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/AnimationVector1D;-><init>(F)V
+HSPLandroidx/compose/animation/core/AnimationVector1D;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/animation/core/AnimationVector1D;->get$animation_core_release(I)F
+HSPLandroidx/compose/animation/core/AnimationVector1D;->getSize$animation_core_release()I
+HSPLandroidx/compose/animation/core/AnimationVector1D;->newVector$animation_core_release()Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/AnimationVector1D;->reset$animation_core_release()V
+HSPLandroidx/compose/animation/core/AnimationVector1D;->set$animation_core_release(FI)V
+HSPLandroidx/compose/animation/core/AnimationVector2D;-><init>(FF)V
+HSPLandroidx/compose/animation/core/AnimationVector3D;-><init>(FFF)V
+HSPLandroidx/compose/animation/core/AnimationVector4D;-><init>(FFFF)V
+HSPLandroidx/compose/animation/core/AnimationVector4D;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/animation/core/AnimationVector4D;->get$animation_core_release(I)F
+HSPLandroidx/compose/animation/core/AnimationVector4D;->getSize$animation_core_release()I
+HSPLandroidx/compose/animation/core/AnimationVector4D;->newVector$animation_core_release()Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/AnimationVector4D;->reset$animation_core_release()V
+HSPLandroidx/compose/animation/core/AnimationVector4D;->set$animation_core_release(FI)V
+HSPLandroidx/compose/animation/core/ComplexDouble;-><init>(DD)V
+HSPLandroidx/compose/animation/core/CubicBezierEasing;-><init>(FFF)V
+HSPLandroidx/compose/animation/core/CubicBezierEasing;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/animation/core/CubicBezierEasing;->transform(F)F
+HSPLandroidx/compose/animation/core/DecayAnimationSpecImpl;-><init>(Landroidx/compose/animation/SplineBasedFloatDecayAnimationSpec;)V
+HSPLandroidx/compose/animation/core/EasingKt;-><clinit>()V
+HSPLandroidx/compose/animation/core/FloatSpringSpec;-><init>(FFF)V
+HSPLandroidx/compose/animation/core/FloatSpringSpec;->getDurationNanos(FFF)J
+HSPLandroidx/compose/animation/core/FloatSpringSpec;->getEndVelocity(FFF)F
+HSPLandroidx/compose/animation/core/FloatSpringSpec;->getValueFromNanos(JFFF)F
+HSPLandroidx/compose/animation/core/FloatSpringSpec;->getVelocityFromNanos(JFFF)F
+HSPLandroidx/compose/animation/core/FloatTweenSpec;-><init>(IILandroidx/compose/animation/core/Easing;)V
+HSPLandroidx/compose/animation/core/FloatTweenSpec;->getValueFromNanos(JFFF)F
+HSPLandroidx/compose/animation/core/FloatTweenSpec;->getVelocityFromNanos(JFFF)F
+HSPLandroidx/compose/animation/core/MutatorMutex$Mutator;-><init>(ILkotlinx/coroutines/Job;)V
+HSPLandroidx/compose/animation/core/MutatorMutex$mutate$2;-><init>(ILandroidx/compose/animation/core/MutatorMutex;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/animation/core/MutatorMutex$mutate$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/animation/core/MutatorMutex$mutate$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/MutatorMutex$mutate$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/MutatorMutex;-><init>()V
+HSPLandroidx/compose/animation/core/SpringSimulation;-><init>()V
+HSPLandroidx/compose/animation/core/SpringSimulation;->updateValues-IJZedt4$animation_core_release(FFJ)J
+HSPLandroidx/compose/animation/core/SpringSpec;-><init>(FFLjava/lang/Object;)V
+HSPLandroidx/compose/animation/core/SpringSpec;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/animation/core/SpringSpec;->vectorize(Landroidx/compose/animation/core/TwoWayConverterImpl;)Landroidx/compose/animation/core/VectorizedFiniteAnimationSpec;
+HSPLandroidx/compose/animation/core/SuspendAnimationKt$animate$4;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/SuspendAnimationKt$animate$6;-><init>(Lkotlin/jvm/internal/Ref$ObjectRef;Ljava/lang/Object;Landroidx/compose/animation/core/Animation;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationState;FLkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/animation/core/SuspendAnimationKt$animate$6;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/SuspendAnimationKt$animate$7;-><init>(Landroidx/compose/animation/core/AnimationState;I)V
+HSPLandroidx/compose/animation/core/SuspendAnimationKt$animate$9;-><init>(Lkotlin/jvm/internal/Ref$ObjectRef;FLandroidx/compose/animation/core/Animation;Landroidx/compose/animation/core/AnimationState;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/animation/core/SuspendAnimationKt$animate$9;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/TargetBasedAnimation;-><init>(Landroidx/compose/animation/core/AnimationSpec;Landroidx/compose/animation/core/TwoWayConverterImpl;Ljava/lang/Object;Ljava/lang/Object;Landroidx/compose/animation/core/AnimationVector;)V
+HSPLandroidx/compose/animation/core/TargetBasedAnimation;->getDurationNanos()J
+HSPLandroidx/compose/animation/core/TargetBasedAnimation;->getTargetValue()Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/TargetBasedAnimation;->getTypeConverter()Landroidx/compose/animation/core/TwoWayConverterImpl;
+HSPLandroidx/compose/animation/core/TargetBasedAnimation;->getValueFromNanos(J)Ljava/lang/Object;
+HSPLandroidx/compose/animation/core/TargetBasedAnimation;->getVelocityVectorFromNanos(J)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/TargetBasedAnimation;->isInfinite()Z
+HSPLandroidx/compose/animation/core/TweenSpec;-><init>(IILandroidx/compose/animation/core/Easing;)V
+HSPLandroidx/compose/animation/core/TweenSpec;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/animation/core/TweenSpec;->vectorize(Landroidx/compose/animation/core/TwoWayConverterImpl;)Landroidx/compose/animation/core/VectorizedFiniteAnimationSpec;
+HSPLandroidx/compose/animation/core/TwoWayConverterImpl;-><init>(Landroidx/compose/foundation/ImageKt$Image$1$1;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/animation/core/VectorConvertersKt;-><clinit>()V
+HSPLandroidx/compose/animation/core/VectorizedFloatAnimationSpec;-><init>(Landroidx/compose/animation/core/FloatAnimationSpec;)V
+HSPLandroidx/compose/animation/core/VectorizedFloatAnimationSpec;-><init>(Landroidx/compose/ui/input/pointer/util/PointerIdArray;)V
+HSPLandroidx/compose/animation/core/VectorizedFloatAnimationSpec;->getDurationNanos(Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)J
+HSPLandroidx/compose/animation/core/VectorizedFloatAnimationSpec;->getEndVelocity(Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/VectorizedFloatAnimationSpec;->getValueFromNanos(JLandroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/VectorizedFloatAnimationSpec;->getVelocityFromNanos(JLandroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/VectorizedSpringSpec;-><init>(FFLandroidx/compose/animation/core/AnimationVector;)V
+HSPLandroidx/compose/animation/core/VectorizedSpringSpec;->getDurationNanos(Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)J
+HSPLandroidx/compose/animation/core/VectorizedSpringSpec;->getEndVelocity(Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/VectorizedSpringSpec;->getValueFromNanos(JLandroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/VectorizedSpringSpec;->getVelocityFromNanos(JLandroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/VectorizedSpringSpec;->isInfinite()V
+HSPLandroidx/compose/animation/core/VectorizedTweenSpec;-><init>(IILandroidx/compose/animation/core/Easing;)V
+HSPLandroidx/compose/animation/core/VectorizedTweenSpec;->getValueFromNanos(JLandroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/VectorizedTweenSpec;->getVelocityFromNanos(JLandroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/compose/animation/core/VisibilityThresholdsKt;-><clinit>()V
+HSPLandroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect$effectModifier$1;-><init>(Landroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect$onNewSize$1;-><init>(Landroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect;)V
+HSPLandroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect$onNewSize$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect;-><init>(Landroid/content/Context;Landroidx/compose/foundation/OverscrollConfiguration;)V
+HSPLandroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect;->animateToRelease()V
+HSPLandroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect;->getEffectModifier()Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect;->invalidateOverscroll()V
+HSPLandroidx/compose/foundation/AndroidOverscrollKt;-><clinit>()V
+HSPLandroidx/compose/foundation/Api31Impl;-><clinit>()V
+HSPLandroidx/compose/foundation/Api31Impl;->create(Landroid/content/Context;Landroid/util/AttributeSet;)Landroid/widget/EdgeEffect;
+HSPLandroidx/compose/foundation/Api31Impl;->getDistance(Landroid/widget/EdgeEffect;)F
+HSPLandroidx/compose/foundation/BackgroundElement;-><init>(JLandroidx/compose/ui/graphics/Shape;)V
+HSPLandroidx/compose/foundation/BackgroundElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/BackgroundElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/BackgroundElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/foundation/BackgroundNode;-><init>(JLandroidx/compose/ui/graphics/Brush;FLandroidx/compose/ui/graphics/Shape;)V
+HSPLandroidx/compose/foundation/BackgroundNode;->draw(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/compose/foundation/BorderKt$drawRectBorder$1;-><init>(Landroidx/compose/ui/graphics/Brush;JJLkotlin/ResultKt;)V
+HSPLandroidx/compose/foundation/BorderKt$drawRectBorder$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/BorderModifierNode;-><init>(FLandroidx/compose/ui/graphics/Brush;Landroidx/compose/ui/graphics/Shape;)V
+HSPLandroidx/compose/foundation/BorderModifierNodeElement;-><init>(FLandroidx/compose/ui/graphics/Brush;Landroidx/compose/ui/graphics/Shape;)V
+HSPLandroidx/compose/foundation/BorderModifierNodeElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/BorderModifierNodeElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/BorderModifierNodeElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/foundation/BorderStroke;-><init>(FLandroidx/compose/ui/graphics/SolidColor;)V
+HSPLandroidx/compose/foundation/ClipScrollableContainerKt;-><clinit>()V
+HSPLandroidx/compose/foundation/DrawOverscrollModifier;-><init>(Landroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect;)V
+HSPLandroidx/compose/foundation/DrawOverscrollModifier;->draw(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/compose/foundation/FocusableElement;-><init>(Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;)V
+HSPLandroidx/compose/foundation/FocusableElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/FocusableElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/FocusableInteractionNode$emitWithFallback$1;-><init>(Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Landroidx/compose/foundation/interaction/Interaction;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/FocusableInteractionNode$emitWithFallback$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/FocusableInteractionNode$emitWithFallback$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/FocusableInteractionNode;-><init>(Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;)V
+HSPLandroidx/compose/foundation/FocusableInteractionNode;->emitWithFallback(Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Landroidx/compose/foundation/interaction/Interaction;)V
+HSPLandroidx/compose/foundation/FocusableKt$FocusableInNonTouchModeElement$1;-><init>()V
+HSPLandroidx/compose/foundation/FocusableKt;-><clinit>()V
+HSPLandroidx/compose/foundation/FocusableKt;->focusable$default(Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;I)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/FocusableNode$onFocusEvent$1;-><init>(Landroidx/compose/foundation/FocusableNode;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/FocusableNode$onFocusEvent$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/FocusableNode$onFocusEvent$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/FocusableNode;-><init>(Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;)V
+HSPLandroidx/compose/foundation/FocusableNode;->onFocusEvent(Landroidx/compose/ui/focus/FocusStateImpl;)V
+HSPLandroidx/compose/foundation/FocusableNode;->onGloballyPositioned(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/foundation/FocusableNode;->onPlaced(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/foundation/FocusablePinnableContainerNode;->onReset()V
+HSPLandroidx/compose/foundation/FocusableSemanticsNode;-><init>()V
+HSPLandroidx/compose/foundation/FocusedBoundsKt;-><clinit>()V
+HSPLandroidx/compose/foundation/FocusedBoundsNode;->onGloballyPositioned(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/foundation/FocusedBoundsObserverNode;-><init>(Lkotlin/collections/AbstractMap$toString$1;)V
+HSPLandroidx/compose/foundation/FocusedBoundsObserverNode;->getProvidedValues()Landroidx/tv/material3/TabKt;
+HSPLandroidx/compose/foundation/FocusedBoundsObserverNode;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/ImageKt$Image$1$1;-><clinit>()V
+HSPLandroidx/compose/foundation/ImageKt$Image$1$1;-><init>(I)V
+HSPLandroidx/compose/foundation/ImageKt$Image$1$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/ImageKt$Image$1;-><clinit>()V
+HSPLandroidx/compose/foundation/ImageKt$Image$1;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Ljava/util/List;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/ImageKt;->Image(Landroidx/compose/ui/graphics/painter/Painter;Ljava/lang/String;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;FLandroidx/compose/ui/graphics/BlendModeColorFilter;Landroidx/compose/runtime/Composer;II)V
+HSPLandroidx/compose/foundation/ImageKt;->background-bw27NRU(Landroidx/compose/ui/Modifier;JLandroidx/compose/ui/graphics/Shape;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/ImageKt;->checkScrollableContainerConstraints-K40F9xA(JLandroidx/compose/foundation/gestures/Orientation;)V
+HSPLandroidx/compose/foundation/ImageKt;->create(Landroid/content/Context;)Landroid/widget/EdgeEffect;
+HSPLandroidx/compose/foundation/ImageKt;->getDistanceCompat(Landroid/widget/EdgeEffect;)F
+HSPLandroidx/compose/foundation/IndicationKt$indication$2;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/foundation/IndicationKt$indication$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/IndicationKt;-><clinit>()V
+HSPLandroidx/compose/foundation/IndicationModifier;-><init>(Landroidx/compose/foundation/IndicationInstance;)V
+HSPLandroidx/compose/foundation/IndicationModifier;->draw(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/compose/foundation/MutatePriority;-><clinit>()V
+HSPLandroidx/compose/foundation/MutatePriority;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/foundation/MutatorMutex$Mutator;-><init>(Landroidx/compose/foundation/MutatePriority;Lkotlinx/coroutines/Job;)V
+HSPLandroidx/compose/foundation/MutatorMutex$mutateWith$2;-><init>(Landroidx/compose/foundation/MutatePriority;Landroidx/compose/foundation/MutatorMutex;Lkotlin/jvm/functions/Function2;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/MutatorMutex$mutateWith$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/MutatorMutex$mutateWith$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/MutatorMutex$mutateWith$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/MutatorMutex;-><init>()V
+HSPLandroidx/compose/foundation/OverscrollConfiguration;-><init>()V
+HSPLandroidx/compose/foundation/OverscrollConfigurationKt;-><clinit>()V
+HSPLandroidx/compose/foundation/ScrollKt$rememberScrollState$1$1;-><init>(I)V
+HSPLandroidx/compose/foundation/ScrollKt$rememberScrollState$1$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/foundation/ScrollKt$scroll$2$semantics$1$1;-><init>(ZLkotlinx/coroutines/CoroutineScope;Landroidx/tv/foundation/lazy/layout/LazyLayoutSemanticState;)V
+HSPLandroidx/compose/foundation/ScrollKt$scroll$2$semantics$1;-><init>(ZZZLandroidx/compose/foundation/ScrollState;Lkotlinx/coroutines/CoroutineScope;)V
+HSPLandroidx/compose/foundation/ScrollKt$scroll$2;-><init>(Landroidx/compose/foundation/ScrollState;Landroidx/compose/foundation/gestures/FlingBehavior;ZZ)V
+HSPLandroidx/compose/foundation/ScrollKt$scroll$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/ScrollState$canScrollForward$2;-><init>(Landroidx/compose/foundation/ScrollState;I)V
+HSPLandroidx/compose/foundation/ScrollState;-><clinit>()V
+HSPLandroidx/compose/foundation/ScrollState;-><init>(I)V
+HSPLandroidx/compose/foundation/ScrollState;->getValue()I
+HSPLandroidx/compose/foundation/ScrollingLayoutElement;-><init>(Landroidx/compose/foundation/ScrollState;ZZ)V
+HSPLandroidx/compose/foundation/ScrollingLayoutElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/ScrollingLayoutNode;-><init>(Landroidx/compose/foundation/ScrollState;ZZ)V
+HSPLandroidx/compose/foundation/ScrollingLayoutNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/gestures/BringIntoViewRequestPriorityQueue;-><init>()V
+HSPLandroidx/compose/foundation/gestures/BringIntoViewRequestPriorityQueue;->cancelAndRemoveAll(Ljava/util/concurrent/CancellationException;)V
+HSPLandroidx/compose/foundation/gestures/BringIntoViewSpec$Companion$DefaultBringIntoViewSpec$1;-><init>()V
+HSPLandroidx/compose/foundation/gestures/BringIntoViewSpec$Companion$DefaultBringIntoViewSpec$1;->calculateScrollDistance(FFF)F
+HSPLandroidx/compose/foundation/gestures/BringIntoViewSpec$Companion$DefaultBringIntoViewSpec$1;->getScrollAnimationSpec()Landroidx/compose/animation/core/AnimationSpec;
+HSPLandroidx/compose/foundation/gestures/BringIntoViewSpec$Companion;-><clinit>()V
+HSPLandroidx/compose/foundation/gestures/BringIntoViewSpec;-><clinit>()V
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$Request;-><init>(Landroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$1$1;Lkotlinx/coroutines/CancellableContinuationImpl;)V
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2$1;-><init>(Landroidx/compose/foundation/gestures/ContentInViewNode;Lkotlinx/coroutines/Job;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2;-><init>(Landroidx/compose/foundation/gestures/ContentInViewNode;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode;-><init>(Landroidx/compose/foundation/gestures/Orientation;Landroidx/compose/foundation/gestures/ScrollableState;ZLandroidx/compose/foundation/gestures/BringIntoViewSpec;)V
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode;->access$calculateScrollDelta(Landroidx/compose/foundation/gestures/ContentInViewNode;)F
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode;->isMaxVisible-O0kMr_c(Landroidx/compose/ui/geometry/Rect;J)Z
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode;->launchAnimation()V
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode;->onPlaced(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode;->onRemeasured-ozmzZPI(J)V
+HSPLandroidx/compose/foundation/gestures/ContentInViewNode;->relocationOffset-BMxPBkI(Landroidx/compose/ui/geometry/Rect;J)J
+HSPLandroidx/compose/foundation/gestures/DefaultFlingBehavior;-><init>(Landroidx/compose/animation/core/DecayAnimationSpecImpl;)V
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2$1;-><init>(Landroidx/compose/foundation/gestures/DefaultScrollableState;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2;-><init>(Landroidx/compose/foundation/gestures/DefaultScrollableState;Landroidx/compose/foundation/MutatePriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scrollScope$1;-><init>(Landroidx/compose/foundation/gestures/DefaultScrollableState;)V
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState$scrollScope$1;->scrollBy(F)F
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState;-><init>(Lkotlin/collections/AbstractMap$toString$1;)V
+HSPLandroidx/compose/foundation/gestures/DefaultScrollableState;->scroll(Landroidx/compose/foundation/MutatePriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/DraggableKt$awaitDrag$2;-><init>(ILjava/lang/Object;Ljava/lang/Object;Z)V
+HSPLandroidx/compose/foundation/gestures/DraggableKt$awaitDrag$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/DraggableNode$onAttach$1;-><init>(Landroidx/compose/foundation/gestures/DraggableNode;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/DraggableNode$onAttach$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/gestures/DraggableNode$onAttach$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/DraggableNode$pointerInputNode$1;-><init>(Landroidx/compose/foundation/gestures/DraggableNode;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/DraggableNode;-><init>(Landroidx/compose/foundation/gestures/ScrollDraggableState;Landroidx/compose/foundation/gestures/Orientation;ZLandroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Landroidx/compose/ui/node/LayoutNode$_foldedChildren$1;Landroidx/compose/foundation/gestures/ScrollableKt$NoOpOnDragStarted$1;Landroidx/compose/foundation/gestures/ScrollableGesturesNode$onDragStopped$1;)V
+HSPLandroidx/compose/foundation/gestures/DraggableNode;->onAttach()V
+HSPLandroidx/compose/foundation/gestures/ModifierLocalScrollableContainerProvider;-><init>(Z)V
+HSPLandroidx/compose/foundation/gestures/ModifierLocalScrollableContainerProvider;->getProvidedValues()Landroidx/tv/material3/TabKt;
+HSPLandroidx/compose/foundation/gestures/MouseWheelScrollNode$1;-><init>(Landroidx/compose/foundation/gestures/MouseWheelScrollNode;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/MouseWheelScrollNode;-><init>(Landroidx/compose/foundation/gestures/ScrollingLogic;)V
+HSPLandroidx/compose/foundation/gestures/MouseWheelScrollNode;->onAttach()V
+HSPLandroidx/compose/foundation/gestures/Orientation;-><clinit>()V
+HSPLandroidx/compose/foundation/gestures/Orientation;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/foundation/gestures/ScrollDraggableState;-><init>(Landroidx/compose/foundation/gestures/ScrollingLogic;)V
+HSPLandroidx/compose/foundation/gestures/ScrollableElement;-><init>(Landroidx/compose/foundation/gestures/ScrollableState;Landroidx/compose/foundation/gestures/Orientation;Landroidx/compose/foundation/OverscrollEffect;ZZLandroidx/compose/foundation/gestures/FlingBehavior;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Landroidx/compose/foundation/gestures/BringIntoViewSpec;)V
+HSPLandroidx/compose/foundation/gestures/ScrollableElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/gestures/ScrollableGesturesNode$onDragStopped$1;-><init>(Landroidx/compose/foundation/gestures/ScrollableGesturesNode;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/ScrollableGesturesNode;-><init>(Landroidx/compose/foundation/gestures/ScrollingLogic;Landroidx/compose/foundation/gestures/Orientation;ZLandroidx/compose/ui/input/nestedscroll/NestedScrollDispatcher;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;)V
+HSPLandroidx/compose/foundation/gestures/ScrollableKt$NoOpOnDragStarted$1;-><init>(ILkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/ScrollableKt$UnityDensity$1;->getDensity()F
+HSPLandroidx/compose/foundation/gestures/ScrollableKt;-><clinit>()V
+HSPLandroidx/compose/foundation/gestures/ScrollableKt;->scrollable$default(Landroidx/compose/foundation/gestures/ScrollableState;Landroidx/compose/foundation/gestures/Orientation;Landroidx/compose/foundation/OverscrollEffect;ZZLandroidx/compose/foundation/gestures/FlingBehavior;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Landroidx/tv/foundation/TvBringIntoViewSpec;I)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/gestures/ScrollableNestedScrollConnection;-><init>(Landroidx/compose/foundation/gestures/ScrollingLogic;Z)V
+HSPLandroidx/compose/foundation/gestures/ScrollableNode;-><init>(Landroidx/compose/foundation/gestures/ScrollableState;Landroidx/compose/foundation/gestures/Orientation;Landroidx/compose/foundation/OverscrollEffect;ZZLandroidx/compose/foundation/gestures/FlingBehavior;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Landroidx/compose/foundation/gestures/BringIntoViewSpec;)V
+HSPLandroidx/compose/foundation/gestures/ScrollableNode;->applyFocusProperties(Landroidx/compose/ui/focus/FocusProperties;)V
+HSPLandroidx/compose/foundation/gestures/ScrollableNode;->onAttach()V
+HSPLandroidx/compose/foundation/gestures/ScrollableState;->scroll$default(Landroidx/compose/foundation/gestures/ScrollableState;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/ScrollingLogic;-><init>(Landroidx/compose/foundation/gestures/ScrollableState;Landroidx/compose/foundation/gestures/Orientation;Landroidx/compose/foundation/OverscrollEffect;ZLandroidx/compose/foundation/gestures/FlingBehavior;Landroidx/compose/ui/input/nestedscroll/NestedScrollDispatcher;)V
+HSPLandroidx/compose/foundation/gestures/UpdatableAnimationState$animateToZero$1;-><init>(Landroidx/compose/foundation/gestures/UpdatableAnimationState;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/gestures/UpdatableAnimationState$animateToZero$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/UpdatableAnimationState$animateToZero$4;-><init>(Landroidx/compose/foundation/gestures/UpdatableAnimationState;FLkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/foundation/gestures/UpdatableAnimationState$animateToZero$4;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/gestures/UpdatableAnimationState;-><clinit>()V
+HSPLandroidx/compose/foundation/gestures/UpdatableAnimationState;-><init>(Landroidx/compose/animation/core/AnimationSpec;)V
+HSPLandroidx/compose/foundation/gestures/UpdatableAnimationState;->animateToZero(Landroidx/compose/foundation/layout/OffsetNode$measure$1;Landroidx/compose/ui/node/LayoutNode$_foldedChildren$1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/interaction/FocusInteraction$Unfocus;-><init>(Landroidx/compose/foundation/interaction/FocusInteraction$Focus;)V
+HSPLandroidx/compose/foundation/interaction/FocusInteractionKt$collectIsFocusedAsState$1$1$1;-><init>(Ljava/util/ArrayList;Landroidx/compose/runtime/MutableState;I)V
+HSPLandroidx/compose/foundation/interaction/FocusInteractionKt$collectIsFocusedAsState$1$1$1;->emit(Landroidx/compose/foundation/interaction/Interaction;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/interaction/FocusInteractionKt$collectIsFocusedAsState$1$1$1;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/interaction/FocusInteractionKt$collectIsFocusedAsState$1$1;-><init>(Landroidx/compose/foundation/interaction/InteractionSource;Landroidx/compose/runtime/MutableState;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/interaction/FocusInteractionKt$collectIsFocusedAsState$1$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/interaction/FocusInteractionKt$collectIsFocusedAsState$1$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/interaction/MutableInteractionSourceImpl;-><init>()V
+HSPLandroidx/compose/foundation/interaction/MutableInteractionSourceImpl;->emit(Landroidx/compose/foundation/interaction/Interaction;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/interaction/PressInteraction$Press;-><init>(J)V
+HSPLandroidx/compose/foundation/interaction/PressInteractionKt$collectIsPressedAsState$1$1;-><init>(Landroidx/compose/foundation/interaction/InteractionSource;Landroidx/compose/runtime/MutableState;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/interaction/PressInteractionKt$collectIsPressedAsState$1$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/interaction/PressInteractionKt$collectIsPressedAsState$1$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/layout/Arrangement$Center$1;-><init>(I)V
+HSPLandroidx/compose/foundation/layout/Arrangement$Center$1;->arrange(ILandroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;[I[I)V
+HSPLandroidx/compose/foundation/layout/Arrangement$Center$1;->getSpacing-D9Ej5fM()F
+HSPLandroidx/compose/foundation/layout/Arrangement$End$1;-><init>(I)V
+HSPLandroidx/compose/foundation/layout/Arrangement$SpacedAligned;-><init>(F)V
+HSPLandroidx/compose/foundation/layout/Arrangement$SpacedAligned;->arrange(ILandroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;[I[I)V
+HSPLandroidx/compose/foundation/layout/Arrangement$SpacedAligned;->arrange(Landroidx/compose/ui/unit/Density;I[I[I)V
+HSPLandroidx/compose/foundation/layout/Arrangement$SpacedAligned;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/layout/Arrangement$SpacedAligned;->getSpacing-D9Ej5fM()F
+HSPLandroidx/compose/foundation/layout/Arrangement$Top$1;-><init>(I)V
+HSPLandroidx/compose/foundation/layout/Arrangement$Top$1;->arrange(Landroidx/compose/ui/unit/Density;I[I[I)V
+HSPLandroidx/compose/foundation/layout/Arrangement;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/Arrangement;->placeCenter$foundation_layout_release(I[I[IZ)V
+HSPLandroidx/compose/foundation/layout/Arrangement;->placeLeftOrTop$foundation_layout_release([I[IZ)V
+HSPLandroidx/compose/foundation/layout/BoxKt$Box$2;-><init>(IILjava/lang/Object;)V
+HSPLandroidx/compose/foundation/layout/BoxKt$Box$2;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/foundation/layout/BoxKt$Box$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/layout/BoxKt$EmptyBoxMeasurePolicy$1;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/BoxKt$EmptyBoxMeasurePolicy$1;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Ljava/util/List;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/layout/BoxKt$boxMeasurePolicy$1$2;-><init>(Landroidx/compose/ui/layout/Placeable;Landroidx/compose/ui/layout/Measurable;Landroidx/compose/ui/layout/MeasureScope;IILandroidx/compose/ui/Alignment;)V
+HSPLandroidx/compose/foundation/layout/BoxKt$boxMeasurePolicy$1$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/layout/BoxKt$boxMeasurePolicy$1;-><init>(Z)V
+HSPLandroidx/compose/foundation/layout/BoxKt$boxMeasurePolicy$1;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Ljava/util/List;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/layout/BoxKt;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/BoxKt;->Box(Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/foundation/layout/BoxKt;->access$placeInBox(Landroidx/compose/ui/layout/Placeable$PlacementScope;Landroidx/compose/ui/layout/Placeable;Landroidx/compose/ui/layout/Measurable;Landroidx/compose/ui/unit/LayoutDirection;IILandroidx/compose/ui/Alignment;)V
+HSPLandroidx/compose/foundation/layout/BoxKt;->rememberBoxMeasurePolicy(ZLandroidx/compose/runtime/Composer;)Landroidx/compose/ui/layout/MeasurePolicy;
+HSPLandroidx/compose/foundation/layout/BoxScopeInstance;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/ColumnKt;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/ColumnKt;->columnMeasurePolicy(Landroidx/compose/foundation/layout/Arrangement$Vertical;Landroidx/compose/runtime/Composer;)Landroidx/compose/ui/layout/MeasurePolicy;
+HSPLandroidx/compose/foundation/layout/CrossAxisAlignment$VerticalCrossAxisAlignment;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/foundation/layout/CrossAxisAlignment$VerticalCrossAxisAlignment;->align$foundation_layout_release(ILandroidx/compose/ui/unit/LayoutDirection;)I
+HSPLandroidx/compose/foundation/layout/FillElement;-><init>(IF)V
+HSPLandroidx/compose/foundation/layout/FillElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/layout/FillElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/layout/FillNode;-><init>(IF)V
+HSPLandroidx/compose/foundation/layout/FillNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/layout/HorizontalAlignElement;-><init>()V
+HSPLandroidx/compose/foundation/layout/HorizontalAlignElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/layout/HorizontalAlignElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/layout/HorizontalAlignNode;-><init>(Landroidx/compose/ui/Alignment$Horizontal;)V
+HSPLandroidx/compose/foundation/layout/HorizontalAlignNode;->modifyParentData(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/layout/OffsetElement;-><init>(FF)V
+HSPLandroidx/compose/foundation/layout/OffsetElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/layout/OffsetElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/layout/OffsetKt;->Spacer(Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;)V
+HSPLandroidx/compose/foundation/layout/OffsetKt;->access$intrinsicSize(Ljava/util/List;Landroidx/compose/ui/CombinedModifier$toString$1;Landroidx/compose/ui/CombinedModifier$toString$1;IIII)I
+HSPLandroidx/compose/foundation/layout/OffsetKt;->getRowColumnParentData(Landroidx/compose/ui/layout/Measurable;)Landroidx/compose/foundation/layout/RowColumnParentData;
+HSPLandroidx/compose/foundation/layout/OffsetKt;->getWeight(Landroidx/compose/foundation/layout/RowColumnParentData;)F
+HSPLandroidx/compose/foundation/layout/OffsetKt;->offset-VpY3zN4(Landroidx/compose/ui/Modifier;FF)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/layout/OffsetKt;->padding-3ABfNKs(Landroidx/compose/ui/Modifier;F)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/layout/OffsetKt;->padding-VpY3zN4(FF)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/layout/OffsetKt;->size(Landroidx/compose/ui/Alignment;Z)Landroidx/compose/foundation/layout/WrapContentElement;
+HSPLandroidx/compose/foundation/layout/OffsetKt;->toBoxConstraints-OenEA2s(JI)J
+HSPLandroidx/compose/foundation/layout/OffsetNode$measure$1;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V
+HSPLandroidx/compose/foundation/layout/OffsetNode$measure$1;->invoke(Landroidx/compose/ui/layout/Placeable$PlacementScope;)V
+HSPLandroidx/compose/foundation/layout/OffsetNode$measure$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/layout/OffsetNode;-><init>(FFZ)V
+HSPLandroidx/compose/foundation/layout/OffsetNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/layout/PaddingElement;-><init>(FFFF)V
+HSPLandroidx/compose/foundation/layout/PaddingElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/layout/PaddingElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/layout/PaddingNode;-><init>(FFFFZ)V
+HSPLandroidx/compose/foundation/layout/PaddingNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/layout/PaddingValuesImpl;-><init>(FFFF)V
+HSPLandroidx/compose/foundation/layout/RowColumnImplKt$rowColumnMeasurePolicy$1;-><init>(ILkotlin/jvm/functions/Function5;FLandroidx/compose/foundation/layout/CrossAxisAlignment$VerticalCrossAxisAlignment;)V
+HSPLandroidx/compose/foundation/layout/RowColumnImplKt$rowColumnMeasurePolicy$1;->maxIntrinsicHeight(Landroidx/compose/ui/node/NodeCoordinator;Ljava/util/List;I)I
+HSPLandroidx/compose/foundation/layout/RowColumnImplKt$rowColumnMeasurePolicy$1;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Ljava/util/List;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/layout/RowColumnMeasureHelperResult;-><init>(III[I)V
+HSPLandroidx/compose/foundation/layout/RowColumnMeasurementHelper;-><init>(ILkotlin/jvm/functions/Function5;FILandroidx/compose/foundation/layout/OffsetKt;Ljava/util/List;[Landroidx/compose/ui/layout/Placeable;)V
+HSPLandroidx/compose/foundation/layout/RowColumnParentData;-><init>()V
+HSPLandroidx/compose/foundation/layout/RowKt$DefaultRowMeasurePolicy$1;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/RowKt$DefaultRowMeasurePolicy$1;-><init>(I)V
+HSPLandroidx/compose/foundation/layout/RowKt$DefaultRowMeasurePolicy$1;->invoke(ILandroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;[I[I)V
+HSPLandroidx/compose/foundation/layout/RowKt$DefaultRowMeasurePolicy$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/io/Serializable;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/layout/RowKt$rowMeasurePolicy$1$1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/foundation/layout/RowKt$rowMeasurePolicy$1$1;->invoke(ILandroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;[I[I)V
+HSPLandroidx/compose/foundation/layout/RowKt$rowMeasurePolicy$1$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/io/Serializable;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/layout/RowKt;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/RowKt;->rowMeasurePolicy(Landroidx/compose/foundation/layout/Arrangement$Horizontal;Landroidx/compose/ui/BiasAlignment$Vertical;Landroidx/compose/runtime/Composer;)Landroidx/compose/ui/layout/MeasurePolicy;
+HSPLandroidx/compose/foundation/layout/RowScopeInstance;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/SizeElement;-><init>(FFFFI)V
+HSPLandroidx/compose/foundation/layout/SizeElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/layout/SizeKt;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/SizeKt;->fillMaxWidth$default(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/layout/SizeKt;->height-3ABfNKs(Landroidx/compose/ui/Modifier;F)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/layout/SizeKt;->width-3ABfNKs(Landroidx/compose/ui/Modifier;F)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/layout/SizeKt;->wrapContentSize$default(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/foundation/layout/SizeNode;-><init>(FFFFZ)V
+HSPLandroidx/compose/foundation/layout/SizeNode;->getTargetConstraints-OenEA2s(Landroidx/compose/ui/unit/Density;)J
+HSPLandroidx/compose/foundation/layout/SizeNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/layout/SpacerMeasurePolicy;-><clinit>()V
+HSPLandroidx/compose/foundation/layout/SpacerMeasurePolicy;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Ljava/util/List;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/layout/WrapContentElement;-><init>(IZLcom/example/tvcomposebasedtests/tvComponents/AppKt$App$1;Ljava/lang/Object;)V
+HSPLandroidx/compose/foundation/layout/WrapContentElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/layout/WrapContentElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/layout/WrapContentNode$measure$1;-><init>(Landroidx/compose/foundation/layout/WrapContentNode;ILandroidx/compose/ui/layout/Placeable;ILandroidx/compose/ui/layout/MeasureScope;)V
+HSPLandroidx/compose/foundation/layout/WrapContentNode$measure$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/layout/WrapContentNode;-><init>(IZLkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/foundation/layout/WrapContentNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/lazy/layout/DefaultLazyKey;-><clinit>()V
+HSPLandroidx/compose/foundation/lazy/layout/DefaultLazyKey;-><init>(I)V
+HSPLandroidx/compose/foundation/lazy/layout/DefaultLazyKey;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/lazy/layout/DefaultLazyKey;->hashCode()I
+HSPLandroidx/compose/foundation/lazy/layout/IntervalList$Interval;-><init>(IILandroidx/compose/foundation/lazy/layout/LazyLayoutIntervalContent$Interval;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory$CachedItemContent;-><init>(Landroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;ILjava/lang/Object;Ljava/lang/Object;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory$CachedItemContent;->getContent()Lkotlin/jvm/functions/Function2;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;-><init>(Landroidx/compose/runtime/saveable/SaveableStateHolder;Landroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3$itemContentFactory$1$1;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;->getContent(Ljava/lang/Object;ILjava/lang/Object;)Lkotlin/jvm/functions/Function2;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;->getContentType(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutItemReusePolicy;-><init>(Landroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutItemReusePolicy;->areCompatible(Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutItemReusePolicy;->getSlotsToRetain(Landroidx/compose/ui/layout/SubcomposeSlotReusePolicy$SlotIdsSet;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3$2$1;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3$2$1;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3$2$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3$itemContentFactory$1$1;-><init>(Landroidx/compose/runtime/State;I)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3$itemContentFactory$1$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3;-><init>(Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function2;ILandroidx/compose/runtime/MutableState;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;-><init>(Landroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;Landroidx/compose/ui/layout/SubcomposeMeasureScope;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;->getLayoutDirection()Landroidx/compose/ui/unit/LayoutDirection;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;->isLookingAhead()Z
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;->layout(IILjava/util/Map;Lkotlin/jvm/functions/Function1;)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;->measure-0kLqBqw(JI)Ljava/util/List;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;->roundToPx-0680j_4(F)I
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem;-><init>(Ljava/lang/Object;Landroidx/compose/foundation/lazy/layout/LazyLayoutPinnedItemList;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem;->getPinsCount()I
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem;->pin()Landroidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem;->release()V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPinnedItemList;-><init>()V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPinnedItemList;->get(I)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPinnedItemList;->isEmpty()Z
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPinnedItemList;->size()I
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState;->schedulePrefetch-0kLqBqw(JI)Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState$PrefetchHandle;
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher$PrefetchRequest;-><init>(IJ)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher$PrefetchRequest;->cancel()V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher;-><init>(Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState;Landroidx/compose/ui/layout/SubcomposeLayoutState;Landroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;Landroid/view/View;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher;->doFrame(J)V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher;->onRemembered()V
+HSPLandroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher;->run()V
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder$1;-><init>(Landroidx/compose/runtime/saveable/SaveableStateRegistry;I)V
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder$SaveableStateProvider$2$invoke$$inlined$onDispose$1;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder$SaveableStateProvider$2$invoke$$inlined$onDispose$1;->dispose()V
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder;-><init>(Landroidx/compose/runtime/saveable/SaveableStateRegistry;Ljava/util/Map;)V
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder;->SaveableStateProvider(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder;->canBeSaved(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder;->consumeRestored(Ljava/lang/String;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder;->performSave()Ljava/util/Map;
+HSPLandroidx/compose/foundation/lazy/layout/LazySaveableStateHolder;->registerProvider(Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Landroidx/compose/runtime/saveable/SaveableStateRegistryImpl$registerProvider$3;
+HSPLandroidx/compose/foundation/lazy/layout/MutableIntervalList;-><init>()V
+HSPLandroidx/compose/foundation/lazy/layout/MutableIntervalList;->addInterval(ILandroidx/compose/foundation/lazy/layout/LazyLayoutIntervalContent$Interval;)V
+HSPLandroidx/compose/foundation/lazy/layout/MutableIntervalList;->checkIndexBounds(I)V
+HSPLandroidx/compose/foundation/lazy/layout/MutableIntervalList;->get(I)Landroidx/compose/foundation/lazy/layout/IntervalList$Interval;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewChildNode;-><init>()V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewChildNode;->getLayoutCoordinates()Landroidx/compose/ui/layout/LayoutCoordinates;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewChildNode;->onPlaced(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewKt;-><clinit>()V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewRequesterImpl$bringIntoView$1;-><init>(Landroidx/compose/foundation/relocation/BringIntoViewRequesterImpl;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewRequesterImpl$bringIntoView$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewRequesterImpl;-><init>()V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewRequesterImpl;->bringIntoView(Landroidx/compose/ui/geometry/Rect;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewRequesterNode;-><init>(Landroidx/compose/foundation/relocation/BringIntoViewRequesterImpl;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewRequesterNode;->onAttach()V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewRequesterNode;->onDetach()V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$1$1;-><init>(Landroidx/compose/foundation/relocation/BringIntoViewResponderNode;Landroidx/compose/ui/layout/LayoutCoordinates;Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$1$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$1;-><init>(Landroidx/compose/foundation/relocation/BringIntoViewResponderNode;Landroidx/compose/ui/layout/LayoutCoordinates;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$2;-><init>(Landroidx/compose/foundation/relocation/BringIntoViewResponderNode;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2;-><init>(Landroidx/compose/foundation/relocation/BringIntoViewResponderNode;Landroidx/compose/ui/layout/LayoutCoordinates;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$parentRect$1;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$parentRect$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode;-><init>(Landroidx/compose/foundation/gestures/ContentInViewNode;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode;->access$bringChildIntoView$localRect(Landroidx/compose/foundation/relocation/BringIntoViewResponderNode;Landroidx/compose/ui/layout/LayoutCoordinates;Lkotlin/jvm/functions/Function0;)Landroidx/compose/ui/geometry/Rect;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode;->bringChildIntoView(Landroidx/compose/ui/layout/LayoutCoordinates;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponderNode;->getProvidedValues()Landroidx/tv/material3/TabKt;
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponder_androidKt$defaultBringIntoViewParent$1;-><init>(Landroidx/compose/ui/node/CompositionLocalConsumerModifierNode;)V
+HSPLandroidx/compose/foundation/relocation/BringIntoViewResponder_androidKt$defaultBringIntoViewParent$1;->bringChildIntoView(Landroidx/compose/ui/layout/LayoutCoordinates;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/foundation/shape/CornerBasedShape;-><init>(Landroidx/compose/foundation/shape/CornerSize;Landroidx/compose/foundation/shape/CornerSize;Landroidx/compose/foundation/shape/CornerSize;Landroidx/compose/foundation/shape/CornerSize;)V
+HSPLandroidx/compose/foundation/shape/CornerBasedShape;->createOutline-Pq9zytI(JLandroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/ui/unit/Density;)Landroidx/compose/ui/graphics/BrushKt;
+HSPLandroidx/compose/foundation/shape/DpCornerSize;-><init>(F)V
+HSPLandroidx/compose/foundation/shape/PercentCornerSize;-><init>(F)V
+HSPLandroidx/compose/foundation/shape/PercentCornerSize;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/shape/PercentCornerSize;->toPx-TmRCtEA(JLandroidx/compose/ui/unit/Density;)F
+HSPLandroidx/compose/foundation/shape/RoundedCornerShape;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/shape/RoundedCornerShapeKt;-><clinit>()V
+HSPLandroidx/compose/foundation/shape/RoundedCornerShapeKt;->RoundedCornerShape-0680j_4(F)Landroidx/compose/foundation/shape/RoundedCornerShape;
+HSPLandroidx/compose/foundation/text/EmptyMeasurePolicy;-><clinit>()V
+HSPLandroidx/compose/foundation/text/EmptyMeasurePolicy;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Ljava/util/List;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/text/modifiers/InlineDensity;-><clinit>()V
+HSPLandroidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache;-><init>(Landroidx/compose/ui/text/AnnotatedString;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/font/FontFamily$Resolver;IZIILjava/util/List;)V
+HSPLandroidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache;->intrinsicHeight(ILandroidx/compose/ui/unit/LayoutDirection;)I
+HSPLandroidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache;->layoutText-K40F9xA(JLandroidx/compose/ui/unit/LayoutDirection;)Landroidx/compose/ui/text/MultiParagraph;
+HSPLandroidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache;->setDensity$foundation_release(Landroidx/compose/ui/unit/Density;)V
+HSPLandroidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache;->setLayoutDirection(Landroidx/compose/ui/unit/LayoutDirection;)Landroidx/compose/ui/text/MultiParagraphIntrinsics;
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringElement;-><init>(Landroidx/compose/ui/text/AnnotatedString;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/font/FontFamily$Resolver;Lkotlin/jvm/functions/Function1;IZII)V
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;-><init>(Landroidx/compose/ui/text/AnnotatedString;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/font/FontFamily$Resolver;Lkotlin/jvm/functions/Function1;IZIILjava/util/List;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->doInvalidations(ZZZZ)V
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->draw(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->getLayoutCache()Landroidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache;
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->getLayoutCache(Landroidx/compose/ui/unit/Density;)Landroidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache;
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->getTextSubstitution()Landroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode$TextSubstitutionValue;
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->maxIntrinsicHeight(Landroidx/compose/ui/layout/IntrinsicMeasureScope;Landroidx/compose/ui/layout/Measurable;I)I
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->maxIntrinsicWidth(Landroidx/compose/ui/layout/IntrinsicMeasureScope;Landroidx/compose/ui/layout/Measurable;I)I
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->updateCallbacks(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Z
+HSPLandroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;->updateLayoutRelatedArgs-MPT68mk(Landroidx/compose/ui/text/TextStyle;Ljava/util/List;IIZLandroidx/compose/ui/text/font/FontFamily$Resolver;I)Z
+HSPLandroidx/compose/foundation/text/selection/SelectionRegistrarKt;-><clinit>()V
+HSPLandroidx/compose/foundation/text/selection/TextSelectionColors;-><init>(JJ)V
+HSPLandroidx/compose/foundation/text/selection/TextSelectionColorsKt;-><clinit>()V
+HSPLandroidx/compose/material/ripple/PlatformRipple;-><init>(ZFLandroidx/compose/runtime/MutableState;)V
+HSPLandroidx/compose/material/ripple/RippleAlpha;-><init>(FFFF)V
+HSPLandroidx/compose/material/ripple/RippleKt;-><clinit>()V
+HSPLandroidx/compose/material/ripple/RippleThemeKt;-><clinit>()V
+HSPLandroidx/compose/material3/ColorScheme;-><init>(JJJJJJJJJJJJJJJJJJJJJJJJJJJJJ)V
+HSPLandroidx/compose/material3/ColorScheme;->getBackground-0d7_KjU()J
+HSPLandroidx/compose/material3/ColorScheme;->getPrimary-0d7_KjU()J
+HSPLandroidx/compose/material3/ColorScheme;->getSurface-0d7_KjU()J
+HSPLandroidx/compose/material3/ColorSchemeKt;-><clinit>()V
+HSPLandroidx/compose/material3/ColorSchemeKt;->darkColorScheme-G1PFc-w$default(JJJI)Landroidx/compose/material3/ColorScheme;
+HSPLandroidx/compose/material3/MaterialRippleTheme;-><clinit>()V
+HSPLandroidx/compose/material3/MaterialThemeKt$MaterialTheme$2;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;III)V
+HSPLandroidx/compose/material3/ShapeDefaults;-><clinit>()V
+HSPLandroidx/compose/material3/Shapes;-><init>(Landroidx/compose/foundation/shape/RoundedCornerShape;Landroidx/compose/foundation/shape/RoundedCornerShape;Landroidx/compose/foundation/shape/RoundedCornerShape;I)V
+HSPLandroidx/compose/material3/ShapesKt$LocalShapes$1;-><clinit>()V
+HSPLandroidx/compose/material3/ShapesKt$LocalShapes$1;-><init>(I)V
+HSPLandroidx/compose/material3/ShapesKt$LocalShapes$1;->invoke()Landroidx/compose/ui/node/LayoutNode;
+HSPLandroidx/compose/material3/ShapesKt$LocalShapes$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/material3/ShapesKt;-><clinit>()V
+HSPLandroidx/compose/material3/TextKt$ProvideTextStyle$1;-><init>(IILjava/lang/Object;Ljava/lang/Object;)V
+HSPLandroidx/compose/material3/TextKt$ProvideTextStyle$1;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/material3/TextKt$ProvideTextStyle$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/material3/TextKt$Text$1;-><clinit>()V
+HSPLandroidx/compose/material3/TextKt$Text$1;-><init>()V
+HSPLandroidx/compose/material3/TextKt$Text$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/material3/TextKt;-><clinit>()V
+HSPLandroidx/compose/material3/TextKt;->ProvideTextStyle(Landroidx/compose/ui/text/TextStyle;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/material3/TextKt;->Text-fLXpl1I(Ljava/lang/String;Landroidx/compose/ui/Modifier;JJLandroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontFamily;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/text/style/TextAlign;JIZILkotlin/jvm/functions/Function1;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/runtime/Composer;III)V
+HSPLandroidx/compose/material3/Typography;-><init>(Landroidx/compose/ui/text/TextStyle;I)V
+HSPLandroidx/compose/material3/TypographyKt;-><clinit>()V
+HSPLandroidx/compose/material3/tokens/ColorDarkTokens;-><clinit>()V
+HSPLandroidx/compose/material3/tokens/PaletteTokens;-><clinit>()V
+HSPLandroidx/compose/material3/tokens/ShapeTokens;-><clinit>()V
+HSPLandroidx/compose/material3/tokens/TypeScaleTokens;-><clinit>()V
+HSPLandroidx/compose/material3/tokens/TypefaceTokens;-><clinit>()V
+HSPLandroidx/compose/material3/tokens/TypographyTokens;-><clinit>()V
+HSPLandroidx/compose/runtime/Anchor;-><init>(I)V
+HSPLandroidx/compose/runtime/BroadcastFrameClock$FrameAwaiter;-><init>(Lkotlin/jvm/functions/Function1;Lkotlinx/coroutines/CancellableContinuationImpl;)V
+HSPLandroidx/compose/runtime/BroadcastFrameClock;-><init>(Landroidx/compose/runtime/Pending$keyMap$2;)V
+HSPLandroidx/compose/runtime/BroadcastFrameClock;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/BroadcastFrameClock;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLandroidx/compose/runtime/BroadcastFrameClock;->sendFrame(J)V
+HSPLandroidx/compose/runtime/BroadcastFrameClock;->withFrameNanos(Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/ComposableSingletons$CompositionKt;-><clinit>()V
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextHolder;-><init>(Landroidx/compose/runtime/ComposerImpl$CompositionContextImpl;)V
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextHolder;->onRemembered()V
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;-><init>(Landroidx/compose/runtime/ComposerImpl;IZLandroidx/compose/runtime/CompositionObserverHolder;)V
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->composeInitial$runtime_release(Landroidx/compose/runtime/CompositionImpl;Landroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->doneComposing$runtime_release()V
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->getCollectingParameterInformation$runtime_release()Z
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->getCompositionLocalScope$runtime_release()Landroidx/compose/runtime/PersistentCompositionLocalMap;
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->getCompoundHashKey$runtime_release()I
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->getEffectCoroutineContext()Lkotlin/coroutines/CoroutineContext;
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->getObserverHolder$runtime_release()Landroidx/compose/runtime/CompositionObserverHolder;
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->invalidate$runtime_release(Landroidx/compose/runtime/CompositionImpl;)V
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->registerComposer$runtime_release(Landroidx/compose/runtime/ComposerImpl;)V
+HSPLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->startComposing$runtime_release()V
+HSPLandroidx/compose/runtime/ComposerImpl$derivedStateObserver$1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/ComposerImpl$derivedStateObserver$1;->done()V
+HSPLandroidx/compose/runtime/ComposerImpl$derivedStateObserver$1;->start()V
+HSPLandroidx/compose/runtime/ComposerImpl;-><init>(Landroidx/compose/ui/node/UiApplier;Landroidx/compose/runtime/CompositionContext;Landroidx/compose/runtime/SlotTable;Ljava/util/HashSet;Landroidx/compose/runtime/changelist/ChangeList;Landroidx/compose/runtime/changelist/ChangeList;Landroidx/compose/runtime/CompositionImpl;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->apply(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->changed(F)Z
+HSPLandroidx/compose/runtime/ComposerImpl;->changed(I)Z
+HSPLandroidx/compose/runtime/ComposerImpl;->changed(J)Z
+HSPLandroidx/compose/runtime/ComposerImpl;->changed(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/ComposerImpl;->changed(Z)Z
+HSPLandroidx/compose/runtime/ComposerImpl;->changedInstance(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/ComposerImpl;->cleanUpCompose()V
+HSPLandroidx/compose/runtime/ComposerImpl;->compoundKeyOf(III)I
+HSPLandroidx/compose/runtime/ComposerImpl;->consume(Landroidx/compose/runtime/ProvidableCompositionLocal;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/ComposerImpl;->createFreshInsertTable()V
+HSPLandroidx/compose/runtime/ComposerImpl;->createNode(Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->currentCompositionLocalScope()Landroidx/compose/runtime/PersistentCompositionLocalMap;
+HSPLandroidx/compose/runtime/ComposerImpl;->deactivateToEndGroup(Z)V
+HSPLandroidx/compose/runtime/ComposerImpl;->doCompose(Landroidx/core/content/res/ComplexColorCompat;Landroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->doRecordDownsFor(II)V
+HSPLandroidx/compose/runtime/ComposerImpl;->endDefaults()V
+HSPLandroidx/compose/runtime/ComposerImpl;->endRestartGroup()Landroidx/compose/runtime/RecomposeScopeImpl;
+HSPLandroidx/compose/runtime/ComposerImpl;->endReusableGroup()V
+HSPLandroidx/compose/runtime/ComposerImpl;->endRoot()V
+HSPLandroidx/compose/runtime/ComposerImpl;->enterGroup(ZLandroidx/compose/runtime/Pending;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->getCurrentRecomposeScope$runtime_release()Landroidx/compose/runtime/RecomposeScopeImpl;
+HSPLandroidx/compose/runtime/ComposerImpl;->getDefaultsInvalid()Z
+HSPLandroidx/compose/runtime/ComposerImpl;->getSkipping()Z
+HSPLandroidx/compose/runtime/ComposerImpl;->nextSlot()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/ComposerImpl;->recompose$runtime_release(Landroidx/core/content/res/ComplexColorCompat;)Z
+HSPLandroidx/compose/runtime/ComposerImpl;->recomposeToGroupEnd()V
+HSPLandroidx/compose/runtime/ComposerImpl;->recordUpsAndDowns(III)V
+HSPLandroidx/compose/runtime/ComposerImpl;->skipCurrentGroup()V
+HSPLandroidx/compose/runtime/ComposerImpl;->skipReaderToGroupEnd()V
+HSPLandroidx/compose/runtime/ComposerImpl;->skipToGroupEnd()V
+HSPLandroidx/compose/runtime/ComposerImpl;->start-BaiHCIY(IILandroidx/compose/runtime/OpaqueKey;Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->startDefaults()V
+HSPLandroidx/compose/runtime/ComposerImpl;->startGroup(ILandroidx/compose/runtime/OpaqueKey;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->startReaderGroup(Ljava/lang/Object;Z)V
+HSPLandroidx/compose/runtime/ComposerImpl;->startReplaceableGroup(I)V
+HSPLandroidx/compose/runtime/ComposerImpl;->startRestartGroup(I)Landroidx/compose/runtime/ComposerImpl;
+HSPLandroidx/compose/runtime/ComposerImpl;->startReusableGroup(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->startReusableNode()V
+HSPLandroidx/compose/runtime/ComposerImpl;->startRoot()V
+HSPLandroidx/compose/runtime/ComposerImpl;->tryImminentInvalidation$runtime_release(Landroidx/compose/runtime/RecomposeScopeImpl;Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/ComposerImpl;->updateCompoundKeyWhenWeEnterGroup(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->updateCompoundKeyWhenWeEnterGroupKeyHash(I)V
+HSPLandroidx/compose/runtime/ComposerImpl;->updateCompoundKeyWhenWeExitGroup(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/ComposerImpl;->updateCompoundKeyWhenWeExitGroupKeyHash(I)V
+HSPLandroidx/compose/runtime/ComposerImpl;->updateNodeCount(II)V
+HSPLandroidx/compose/runtime/ComposerImpl;->updateNodeCountOverrides(II)V
+HSPLandroidx/compose/runtime/ComposerImpl;->updateProviderMapGroup(Landroidx/compose/runtime/PersistentCompositionLocalMap;Landroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;)Landroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;
+HSPLandroidx/compose/runtime/ComposerImpl;->updatedNodeCount(I)I
+HSPLandroidx/compose/runtime/ComposerImpl;->useNode()V
+HSPLandroidx/compose/runtime/CompositionContext;->doneComposing$runtime_release()V
+HSPLandroidx/compose/runtime/CompositionContext;->getCompositionLocalScope$runtime_release()Landroidx/compose/runtime/PersistentCompositionLocalMap;
+HSPLandroidx/compose/runtime/CompositionContext;->getObserverHolder$runtime_release()Landroidx/compose/runtime/CompositionObserverHolder;
+HSPLandroidx/compose/runtime/CompositionContext;->registerComposer$runtime_release(Landroidx/compose/runtime/ComposerImpl;)V
+HSPLandroidx/compose/runtime/CompositionContext;->startComposing$runtime_release()V
+HSPLandroidx/compose/runtime/CompositionContextKt;-><clinit>()V
+HSPLandroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;-><init>(Ljava/util/HashSet;)V
+HSPLandroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;->dispatchAbandons()V
+HSPLandroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;->dispatchRememberObservers()V
+HSPLandroidx/compose/runtime/CompositionImpl;-><init>(Landroidx/compose/runtime/CompositionContext;Landroidx/compose/ui/node/UiApplier;)V
+HSPLandroidx/compose/runtime/CompositionImpl;->addPendingInvalidationsLocked(Ljava/util/HashSet;Ljava/lang/Object;Z)Ljava/util/HashSet;
+HSPLandroidx/compose/runtime/CompositionImpl;->addPendingInvalidationsLocked(Ljava/util/Set;Z)V
+HSPLandroidx/compose/runtime/CompositionImpl;->applyChanges()V
+HSPLandroidx/compose/runtime/CompositionImpl;->applyChangesInLocked(Landroidx/compose/runtime/changelist/ChangeList;)V
+HSPLandroidx/compose/runtime/CompositionImpl;->applyLateChanges()V
+HSPLandroidx/compose/runtime/CompositionImpl;->changesApplied()V
+HSPLandroidx/compose/runtime/CompositionImpl;->cleanUpDerivedStateObservations()V
+HSPLandroidx/compose/runtime/CompositionImpl;->composeContent(Landroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLandroidx/compose/runtime/CompositionImpl;->drainPendingModificationsForCompositionLocked()V
+HSPLandroidx/compose/runtime/CompositionImpl;->drainPendingModificationsLocked()V
+HSPLandroidx/compose/runtime/CompositionImpl;->getHasInvalidations()Z
+HSPLandroidx/compose/runtime/CompositionImpl;->invalidate$enumunboxing$(Landroidx/compose/runtime/RecomposeScopeImpl;Ljava/lang/Object;)I
+HSPLandroidx/compose/runtime/CompositionImpl;->invalidateChecked$enumunboxing$(Landroidx/compose/runtime/RecomposeScopeImpl;Landroidx/compose/runtime/Anchor;Ljava/lang/Object;)I
+HSPLandroidx/compose/runtime/CompositionImpl;->invalidateScopeOfLocked(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/CompositionImpl;->isDisposed()Z
+HSPLandroidx/compose/runtime/CompositionImpl;->observer()V
+HSPLandroidx/compose/runtime/CompositionImpl;->observesAnyOf(Landroidx/compose/runtime/collection/IdentityArraySet;)Z
+HSPLandroidx/compose/runtime/CompositionImpl;->recompose()Z
+HSPLandroidx/compose/runtime/CompositionImpl;->recomposeScopeReleased()V
+HSPLandroidx/compose/runtime/CompositionImpl;->recordModificationsOf(Landroidx/compose/runtime/collection/IdentityArraySet;)V
+HSPLandroidx/compose/runtime/CompositionImpl;->recordReadOf(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/CompositionImpl;->recordWriteOf(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/CompositionImpl;->setContent(Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/runtime/CompositionKt;-><clinit>()V
+HSPLandroidx/compose/runtime/CompositionLocal;-><init>(Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/runtime/CompositionLocalMap$Companion;-><clinit>()V
+HSPLandroidx/compose/runtime/CompositionLocalMap;-><clinit>()V
+HSPLandroidx/compose/runtime/CompositionObserverHolder;-><init>()V
+HSPLandroidx/compose/runtime/CompositionScopedCoroutineScopeCanceller;-><init>(Lkotlinx/coroutines/internal/ContextScope;)V
+HSPLandroidx/compose/runtime/CompositionScopedCoroutineScopeCanceller;->onRemembered()V
+HSPLandroidx/compose/runtime/DerivedSnapshotState$ResultRecord;-><clinit>()V
+HSPLandroidx/compose/runtime/DerivedSnapshotState$ResultRecord;-><init>()V
+HSPLandroidx/compose/runtime/DerivedSnapshotState$ResultRecord;->assign(Landroidx/compose/runtime/snapshots/StateRecord;)V
+HSPLandroidx/compose/runtime/DerivedSnapshotState$ResultRecord;->create()Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/DerivedSnapshotState$ResultRecord;->isValid(Landroidx/compose/runtime/DerivedSnapshotState;Landroidx/compose/runtime/snapshots/Snapshot;)Z
+HSPLandroidx/compose/runtime/DerivedSnapshotState$ResultRecord;->readableHash(Landroidx/compose/runtime/DerivedSnapshotState;Landroidx/compose/runtime/snapshots/Snapshot;)I
+HSPLandroidx/compose/runtime/DerivedSnapshotState;-><init>(Landroidx/compose/runtime/ReferentialEqualityPolicy;Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/runtime/DerivedSnapshotState;->currentRecord(Landroidx/compose/runtime/DerivedSnapshotState$ResultRecord;Landroidx/compose/runtime/snapshots/Snapshot;ZLkotlin/jvm/functions/Function0;)Landroidx/compose/runtime/DerivedSnapshotState$ResultRecord;
+HSPLandroidx/compose/runtime/DerivedSnapshotState;->getCurrentRecord()Landroidx/compose/runtime/DerivedSnapshotState$ResultRecord;
+HSPLandroidx/compose/runtime/DerivedSnapshotState;->getFirstStateRecord()Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/DerivedSnapshotState;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/DerivedSnapshotState;->prependStateRecord(Landroidx/compose/runtime/snapshots/StateRecord;)V
+HSPLandroidx/compose/runtime/DisposableEffectImpl;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/runtime/DisposableEffectImpl;->onForgotten()V
+HSPLandroidx/compose/runtime/DisposableEffectImpl;->onRemembered()V
+HSPLandroidx/compose/runtime/DynamicProvidableCompositionLocal;-><init>(Landroidx/compose/runtime/SnapshotMutationPolicy;Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/runtime/DynamicProvidableCompositionLocal;->updatedStateOf$runtime_release(Ljava/lang/Object;Landroidx/compose/runtime/State;)Landroidx/compose/runtime/State;
+HSPLandroidx/compose/runtime/GroupInfo;-><init>(III)V
+HSPLandroidx/compose/runtime/IntStack;-><init>()V
+HSPLandroidx/compose/runtime/IntStack;-><init>(I)V
+HSPLandroidx/compose/runtime/IntStack;->getSize()I
+HSPLandroidx/compose/runtime/IntStack;->pop()I
+HSPLandroidx/compose/runtime/IntStack;->push(I)V
+HSPLandroidx/compose/runtime/IntStack;->pushDiagonal(III)V
+HSPLandroidx/compose/runtime/IntStack;->pushRange(IIII)V
+HSPLandroidx/compose/runtime/IntStack;->quickSort(II)V
+HSPLandroidx/compose/runtime/IntStack;->swapDiagonal(II)V
+HSPLandroidx/compose/runtime/Invalidation;-><init>(Landroidx/compose/runtime/RecomposeScopeImpl;ILandroidx/compose/runtime/collection/IdentityArraySet;)V
+HSPLandroidx/compose/runtime/KeyInfo;-><init>(ILjava/lang/Object;II)V
+HSPLandroidx/compose/runtime/Latch$await$2$2;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/Latch$await$2$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Latch$await$2$2;->invoke(Ljava/lang/Throwable;)V
+HSPLandroidx/compose/runtime/Latch;-><init>()V
+HSPLandroidx/compose/runtime/Latch;-><init>(I)V
+HSPLandroidx/compose/runtime/Latch;->add(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/runtime/Latch;->contains(Landroidx/compose/ui/node/LayoutNode;)Z
+HSPLandroidx/compose/runtime/Latch;->pop()Landroidx/compose/ui/node/LayoutNode;
+HSPLandroidx/compose/runtime/Latch;->remove(Landroidx/compose/ui/node/LayoutNode;)Z
+HSPLandroidx/compose/runtime/LaunchedEffectImpl;-><init>(Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/runtime/LaunchedEffectImpl;->onForgotten()V
+HSPLandroidx/compose/runtime/LaunchedEffectImpl;->onRemembered()V
+HSPLandroidx/compose/runtime/LazyValueHolder;-><init>(Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/runtime/LazyValueHolder;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/MonotonicFrameClock;->getKey()Lkotlin/coroutines/CoroutineContext$Key;
+HSPLandroidx/compose/runtime/OpaqueKey;-><init>(Ljava/lang/String;)V
+HSPLandroidx/compose/runtime/OpaqueKey;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/OpaqueKey;->hashCode()I
+HSPLandroidx/compose/runtime/ParcelableSnapshotMutableFloatState;-><clinit>()V
+HSPLandroidx/compose/runtime/ParcelableSnapshotMutableIntState;-><clinit>()V
+HSPLandroidx/compose/runtime/ParcelableSnapshotMutableState;-><clinit>()V
+HSPLandroidx/compose/runtime/PausableMonotonicFrameClock$withFrameNanos$1;-><init>(Landroidx/compose/runtime/PausableMonotonicFrameClock;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/runtime/PausableMonotonicFrameClock$withFrameNanos$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/PausableMonotonicFrameClock;-><init>(Landroidx/compose/runtime/MonotonicFrameClock;)V
+HSPLandroidx/compose/runtime/PausableMonotonicFrameClock;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/PausableMonotonicFrameClock;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLandroidx/compose/runtime/PausableMonotonicFrameClock;->minusKey(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+HSPLandroidx/compose/runtime/PausableMonotonicFrameClock;->withFrameNanos(Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Pending$keyMap$2;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/Pending$keyMap$2;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Pending;-><init>(ILjava/util/ArrayList;)V
+HSPLandroidx/compose/runtime/ProduceStateScopeImpl;-><init>(Landroidx/compose/runtime/MutableState;Lkotlin/coroutines/CoroutineContext;)V
+HSPLandroidx/compose/runtime/ProduceStateScopeImpl;->setValue(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/ProvidableCompositionLocal;->provides(Ljava/lang/Object;)Landroidx/compose/runtime/ProvidedValue;
+HSPLandroidx/compose/runtime/ProvidedValue;-><init>(Landroidx/compose/runtime/CompositionLocal;Ljava/lang/Object;Z)V
+HSPLandroidx/compose/runtime/RecomposeScopeImpl$end$1$2;-><init>(IILjava/lang/Object;Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/RecomposeScopeImpl$end$1$2;-><init>(Landroidx/compose/runtime/DerivedSnapshotState;Landroidx/core/content/res/ComplexColorCompat;I)V
+HSPLandroidx/compose/runtime/RecomposeScopeImpl$end$1$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/RecomposeScopeImpl;-><init>(Landroidx/compose/runtime/CompositionImpl;)V
+HSPLandroidx/compose/runtime/RecomposeScopeImpl;->invalidateForResult$enumunboxing$(Ljava/lang/Object;)I
+HSPLandroidx/compose/runtime/Recomposer$State;-><clinit>()V
+HSPLandroidx/compose/runtime/Recomposer$State;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/runtime/Recomposer$effectJob$1$1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/Recomposer$effectJob$1$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$join$2;-><init>(Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/runtime/Recomposer$join$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/runtime/Recomposer$join$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$join$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$performRecompose$1$1;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/Recomposer$performRecompose$1$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$performRecompose$1$1;->invoke()V
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2$3;-><init>(Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/MonotonicFrameClock;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2$3;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2$3;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2$3;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2$unregisterApplyObserver$1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2$unregisterApplyObserver$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2$unregisterApplyObserver$1;->invoke(Ljava/util/Set;)V
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2;-><init>(Landroidx/compose/runtime/Recomposer;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/MonotonicFrameClock;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$recompositionRunner$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2$1;-><init>(Landroidx/compose/runtime/Recomposer;Landroidx/compose/runtime/collection/IdentityArraySet;Landroidx/compose/runtime/collection/IdentityArraySet;Ljava/util/List;Ljava/util/List;Ljava/util/Set;Ljava/util/List;Ljava/util/Set;)V
+HSPLandroidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2;-><init>(Landroidx/compose/runtime/Recomposer;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Recomposer;-><clinit>()V
+HSPLandroidx/compose/runtime/Recomposer;-><init>(Lkotlin/coroutines/CoroutineContext;)V
+HSPLandroidx/compose/runtime/Recomposer;->access$performRecompose(Landroidx/compose/runtime/Recomposer;Landroidx/compose/runtime/CompositionImpl;Landroidx/compose/runtime/collection/IdentityArraySet;)Landroidx/compose/runtime/CompositionImpl;
+HSPLandroidx/compose/runtime/Recomposer;->access$recordComposerModifications(Landroidx/compose/runtime/Recomposer;)Z
+HSPLandroidx/compose/runtime/Recomposer;->applyAndCheck(Landroidx/compose/runtime/snapshots/MutableSnapshot;)V
+HSPLandroidx/compose/runtime/Recomposer;->composeInitial$runtime_release(Landroidx/compose/runtime/CompositionImpl;Landroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLandroidx/compose/runtime/Recomposer;->deriveStateLocked()Lkotlinx/coroutines/CancellableContinuation;
+HSPLandroidx/compose/runtime/Recomposer;->getCollectingParameterInformation$runtime_release()Z
+HSPLandroidx/compose/runtime/Recomposer;->getCompoundHashKey$runtime_release()I
+HSPLandroidx/compose/runtime/Recomposer;->getEffectCoroutineContext()Lkotlin/coroutines/CoroutineContext;
+HSPLandroidx/compose/runtime/Recomposer;->getHasBroadcastFrameClockAwaitersLocked()Z
+HSPLandroidx/compose/runtime/Recomposer;->getHasSchedulingWork()Z
+HSPLandroidx/compose/runtime/Recomposer;->getKnownCompositions()Ljava/util/List;
+HSPLandroidx/compose/runtime/Recomposer;->invalidate$runtime_release(Landroidx/compose/runtime/CompositionImpl;)V
+HSPLandroidx/compose/runtime/Recomposer;->performInitialMovableContentInserts(Landroidx/compose/runtime/CompositionImpl;)V
+HSPLandroidx/compose/runtime/ReferentialEqualityPolicy;-><clinit>()V
+HSPLandroidx/compose/runtime/ReferentialEqualityPolicy;->equivalent(Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/SkippableUpdater;-><init>(Landroidx/compose/runtime/Composer;)V
+HSPLandroidx/compose/runtime/SlotReader;-><init>(Landroidx/compose/runtime/SlotTable;)V
+HSPLandroidx/compose/runtime/SlotReader;->anchor(I)Landroidx/compose/runtime/Anchor;
+HSPLandroidx/compose/runtime/SlotReader;->aux([II)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SlotReader;->close()V
+HSPLandroidx/compose/runtime/SlotReader;->endGroup()V
+HSPLandroidx/compose/runtime/SlotReader;->getGroupAux()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SlotReader;->getGroupKey()I
+HSPLandroidx/compose/runtime/SlotReader;->groupGet(II)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SlotReader;->groupSize(I)I
+HSPLandroidx/compose/runtime/SlotReader;->isNode(I)Z
+HSPLandroidx/compose/runtime/SlotReader;->node(I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SlotReader;->nodeCount(I)I
+HSPLandroidx/compose/runtime/SlotReader;->objectKey([II)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SlotReader;->parent(I)I
+HSPLandroidx/compose/runtime/SlotReader;->reposition(I)V
+HSPLandroidx/compose/runtime/SlotReader;->skipGroup()I
+HSPLandroidx/compose/runtime/SlotReader;->skipToGroupEnd()V
+HSPLandroidx/compose/runtime/SlotReader;->startGroup()V
+HSPLandroidx/compose/runtime/SlotTable;-><init>()V
+HSPLandroidx/compose/runtime/SlotTable;->anchorIndex(Landroidx/compose/runtime/Anchor;)I
+HSPLandroidx/compose/runtime/SlotTable;->openReader()Landroidx/compose/runtime/SlotReader;
+HSPLandroidx/compose/runtime/SlotTable;->openWriter()Landroidx/compose/runtime/SlotWriter;
+HSPLandroidx/compose/runtime/SlotTable;->ownsAnchor(Landroidx/compose/runtime/Anchor;)Z
+HSPLandroidx/compose/runtime/SlotWriter;-><clinit>()V
+HSPLandroidx/compose/runtime/SlotWriter;-><init>(Landroidx/compose/runtime/SlotTable;)V
+HSPLandroidx/compose/runtime/SlotWriter;->advanceBy(I)V
+HSPLandroidx/compose/runtime/SlotWriter;->anchor(I)Landroidx/compose/runtime/Anchor;
+HSPLandroidx/compose/runtime/SlotWriter;->auxIndex([II)I
+HSPLandroidx/compose/runtime/SlotWriter;->beginInsert()V
+HSPLandroidx/compose/runtime/SlotWriter;->close()V
+HSPLandroidx/compose/runtime/SlotWriter;->dataIndex([II)I
+HSPLandroidx/compose/runtime/SlotWriter;->dataIndexToDataAddress(I)I
+HSPLandroidx/compose/runtime/SlotWriter;->endInsert()V
+HSPLandroidx/compose/runtime/SlotWriter;->getSize$runtime_release()I
+HSPLandroidx/compose/runtime/SlotWriter;->groupIndexToAddress(I)I
+HSPLandroidx/compose/runtime/SlotWriter;->groupSize(I)I
+HSPLandroidx/compose/runtime/SlotWriter;->markGroup$default(Landroidx/compose/runtime/SlotWriter;)V
+HSPLandroidx/compose/runtime/SlotWriter;->moveFrom(Landroidx/compose/runtime/SlotTable;I)V
+HSPLandroidx/compose/runtime/SlotWriter;->moveGroupGapTo(I)V
+HSPLandroidx/compose/runtime/SlotWriter;->moveSlotGapTo(II)V
+HSPLandroidx/compose/runtime/SlotWriter;->node(I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SlotWriter;->parent(I)I
+HSPLandroidx/compose/runtime/SlotWriter;->parent([II)I
+HSPLandroidx/compose/runtime/SlotWriter;->recalculateMarks()V
+HSPLandroidx/compose/runtime/SlotWriter;->set(IILjava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SlotWriter;->skipToGroupEnd()V
+HSPLandroidx/compose/runtime/SlotWriter;->slotIndex([II)I
+HSPLandroidx/compose/runtime/SlotWriter;->updateAux(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/SlotWriter;->updateContainsMark(I)V
+HSPLandroidx/compose/runtime/SlotWriter;->updateNodeOfGroup(ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/SnapshotMutableFloatStateImpl$FloatStateStateRecord;-><init>(F)V
+HSPLandroidx/compose/runtime/SnapshotMutableFloatStateImpl;-><init>(F)V
+HSPLandroidx/compose/runtime/SnapshotMutableFloatStateImpl;->setFloatValue(F)V
+HSPLandroidx/compose/runtime/SnapshotMutableIntStateImpl$IntStateStateRecord;-><init>(I)V
+HSPLandroidx/compose/runtime/SnapshotMutableIntStateImpl$IntStateStateRecord;->assign(Landroidx/compose/runtime/snapshots/StateRecord;)V
+HSPLandroidx/compose/runtime/SnapshotMutableIntStateImpl$IntStateStateRecord;->create()Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/SnapshotMutableIntStateImpl;-><init>(I)V
+HSPLandroidx/compose/runtime/SnapshotMutableIntStateImpl;->getFirstStateRecord()Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/SnapshotMutableIntStateImpl;->getIntValue()I
+HSPLandroidx/compose/runtime/SnapshotMutableIntStateImpl;->prependStateRecord(Landroidx/compose/runtime/snapshots/StateRecord;)V
+HSPLandroidx/compose/runtime/SnapshotMutableIntStateImpl;->setIntValue(I)V
+HSPLandroidx/compose/runtime/SnapshotMutableStateImpl$StateStateRecord;-><init>(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/SnapshotMutableStateImpl$StateStateRecord;->assign(Landroidx/compose/runtime/snapshots/StateRecord;)V
+HSPLandroidx/compose/runtime/SnapshotMutableStateImpl$StateStateRecord;->create()Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/SnapshotMutableStateImpl;-><init>(Ljava/lang/Object;Landroidx/compose/runtime/SnapshotMutationPolicy;)V
+HSPLandroidx/compose/runtime/SnapshotMutableStateImpl;->getFirstStateRecord()Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/SnapshotMutableStateImpl;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SnapshotMutableStateImpl;->prependStateRecord(Landroidx/compose/runtime/snapshots/StateRecord;)V
+HSPLandroidx/compose/runtime/SnapshotMutableStateImpl;->setValue(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/SnapshotStateKt__DerivedStateKt;-><clinit>()V
+HSPLandroidx/compose/runtime/SnapshotStateKt__ProduceStateKt$produceState$3;-><init>(Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/MutableState;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/runtime/SnapshotStateKt__ProduceStateKt$produceState$3;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/runtime/SnapshotStateKt__ProduceStateKt$produceState$3;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$collectAsState$1$1;-><init>(Landroidx/compose/runtime/ProduceStateScopeImpl;I)V
+HSPLandroidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$collectAsState$1$1;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$collectAsState$1;-><init>(Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$collectAsState$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$collectAsState$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$collectAsState$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Stack;-><init>()V
+HSPLandroidx/compose/runtime/Stack;-><init>(I)V
+HSPLandroidx/compose/runtime/Stack;-><init>(II)V
+HSPLandroidx/compose/runtime/Stack;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/Stack;-><init>(Landroid/content/Context;)V
+HSPLandroidx/compose/runtime/Stack;-><init>(Ljava/nio/ByteBuffer;)V
+HSPLandroidx/compose/runtime/Stack;->clear()V
+HSPLandroidx/compose/runtime/Stack;->load(Lokhttp3/MediaType;)V
+HSPLandroidx/compose/runtime/Stack;->pop()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/Stack;->readUnsignedInt()J
+HSPLandroidx/compose/runtime/Stack;->skip(I)V
+HSPLandroidx/compose/runtime/StaticProvidableCompositionLocal;->updatedStateOf$runtime_release(Ljava/lang/Object;Landroidx/compose/runtime/State;)Landroidx/compose/runtime/State;
+HSPLandroidx/compose/runtime/StaticValueHolder;-><init>(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/StaticValueHolder;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/StaticValueHolder;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/StructuralEqualityPolicy;-><clinit>()V
+HSPLandroidx/compose/runtime/StructuralEqualityPolicy;->equivalent(Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/changelist/ChangeList;-><init>()V
+HSPLandroidx/compose/runtime/changelist/ChangeList;->executeAndFlushAllPendingChanges(Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/ChangeList;->isEmpty()Z
+HSPLandroidx/compose/runtime/changelist/ComposerChangeListWriter;-><init>(Landroidx/compose/runtime/ComposerImpl;Landroidx/compose/runtime/changelist/ChangeList;)V
+HSPLandroidx/compose/runtime/changelist/ComposerChangeListWriter;->moveUp()V
+HSPLandroidx/compose/runtime/changelist/ComposerChangeListWriter;->pushPendingUpsAndDowns()V
+HSPLandroidx/compose/runtime/changelist/ComposerChangeListWriter;->realizeNodeMovementOperations()V
+HSPLandroidx/compose/runtime/changelist/ComposerChangeListWriter;->realizeOperationLocation(Z)V
+HSPLandroidx/compose/runtime/changelist/FixupList;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$AdvanceSlotsBy;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$AdvanceSlotsBy;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$AdvanceSlotsBy;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$DeactivateCurrentGroup;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$DeactivateCurrentGroup;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$DeactivateCurrentGroup;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$Downs;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$Downs;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$Downs;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$InsertNodeFixup;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$InsertNodeFixup;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$InsertNodeFixup;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$InsertSlotsWithFixups;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$InsertSlotsWithFixups;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$InsertSlotsWithFixups;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$PostInsertNodeFixup;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$PostInsertNodeFixup;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$PostInsertNodeFixup;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$Remember;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$Remember;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$Remember;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$SideEffect;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$SideEffect;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$SideEffect;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateAuxData;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateAuxData;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateAuxData;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateNode;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateNode;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateNode;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateValue;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateValue;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$UpdateValue;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$Ups;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$Ups;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$Ups;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation$UseCurrentNode;-><clinit>()V
+HSPLandroidx/compose/runtime/changelist/Operation$UseCurrentNode;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operation$UseCurrentNode;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operation;-><init>(II)V
+HSPLandroidx/compose/runtime/changelist/Operation;-><init>(III)V
+HSPLandroidx/compose/runtime/changelist/Operations$OpIterator;-><init>(Landroidx/compose/runtime/changelist/Operations;)V
+HSPLandroidx/compose/runtime/changelist/Operations$OpIterator;->getInt-w8GmfQM(I)I
+HSPLandroidx/compose/runtime/changelist/Operations$OpIterator;->getObject-31yXWZQ(I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/changelist/Operations;-><init>()V
+HSPLandroidx/compose/runtime/changelist/Operations;->access$createExpectedArgMask(Landroidx/compose/runtime/changelist/Operations;I)I
+HSPLandroidx/compose/runtime/changelist/Operations;->clear()V
+HSPLandroidx/compose/runtime/changelist/Operations;->executeAndFlushAllPendingOperations(Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+HSPLandroidx/compose/runtime/changelist/Operations;->peekOperation()Landroidx/compose/runtime/changelist/Operation;
+HSPLandroidx/compose/runtime/changelist/Operations;->push(Landroidx/compose/runtime/changelist/Operation;)V
+HSPLandroidx/compose/runtime/changelist/Operations;->pushOp(Landroidx/compose/runtime/changelist/Operation;)V
+HSPLandroidx/compose/runtime/collection/IdentityArrayIntMap;-><init>()V
+HSPLandroidx/compose/runtime/collection/IdentityArrayIntMap;->add(ILjava/lang/Object;)I
+HSPLandroidx/compose/runtime/collection/IdentityArrayMap$asMap$1$entries$1$iterator$1$1;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/collection/IdentityArrayMap$asMap$1$entries$1$iterator$1$1;->getKey()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/collection/IdentityArrayMap$asMap$1$entries$1$iterator$1$1;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;-><init>()V
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->add(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->addAll(Ljava/util/Collection;)V
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->clear()V
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->contains(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->find(Ljava/lang/Object;)I
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->isEmpty()Z
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->isNotEmpty()Z
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->iterator()Ljava/util/Iterator;
+HSPLandroidx/compose/runtime/collection/IdentityArraySet;->remove(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/collection/MutableVector$MutableVectorList;-><init>(Landroidx/compose/runtime/collection/MutableVector;)V
+HSPLandroidx/compose/runtime/collection/MutableVector$MutableVectorList;->get(I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/collection/MutableVector$MutableVectorList;->indexOf(Ljava/lang/Object;)I
+HSPLandroidx/compose/runtime/collection/MutableVector$MutableVectorList;->isEmpty()Z
+HSPLandroidx/compose/runtime/collection/MutableVector$MutableVectorList;->iterator()Ljava/util/Iterator;
+HSPLandroidx/compose/runtime/collection/MutableVector$MutableVectorList;->size()I
+HSPLandroidx/compose/runtime/collection/MutableVector$VectorListIterator;-><init>(ILjava/util/List;)V
+HSPLandroidx/compose/runtime/collection/MutableVector$VectorListIterator;->hasNext()Z
+HSPLandroidx/compose/runtime/collection/MutableVector$VectorListIterator;->next()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/collection/MutableVector;-><init>([Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/collection/MutableVector;->add(ILjava/lang/Object;)V
+HSPLandroidx/compose/runtime/collection/MutableVector;->add(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/collection/MutableVector;->addAll(ILandroidx/compose/runtime/collection/MutableVector;)V
+HSPLandroidx/compose/runtime/collection/MutableVector;->asMutableList()Ljava/util/List;
+HSPLandroidx/compose/runtime/collection/MutableVector;->clear()V
+HSPLandroidx/compose/runtime/collection/MutableVector;->contains(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/collection/MutableVector;->ensureCapacity(I)V
+HSPLandroidx/compose/runtime/collection/MutableVector;->indexOf(Ljava/lang/Object;)I
+HSPLandroidx/compose/runtime/collection/MutableVector;->isEmpty()Z
+HSPLandroidx/compose/runtime/collection/MutableVector;->isNotEmpty()Z
+HSPLandroidx/compose/runtime/collection/MutableVector;->remove(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/collection/MutableVector;->removeAt(I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/collection/MutableVector;->removeRange(II)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/SmallPersistentVector;-><clinit>()V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/SmallPersistentVector;-><init>([Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/SmallPersistentVector;->add(Ljava/lang/Object;)Landroidx/compose/runtime/external/kotlinx/collections/immutable/PersistentList;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/SmallPersistentVector;->get(I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/SmallPersistentVector;->getSize()I
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/SmallPersistentVector;->indexOf(Ljava/lang/Object;)I
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/SmallPersistentVector;->removeAt(I)Landroidx/compose/runtime/external/kotlinx/collections/immutable/PersistentList;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;-><clinit>()V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;-><init>(Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;I)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;->containsKey(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;->put(Ljava/lang/Object;Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/persistentOrderedSet/Links;)Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBaseIterator;-><init>(Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;[Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNodeBaseIterator;)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBaseIterator;->ensureNextEntryIsReady()V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBaseIterator;->hasNext()Z
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBaseIterator;->moveToNextNodeWithData(I)I
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBaseIterator;->next()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBuilder;-><init>(Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBuilder;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBuilder;->putAll(Ljava/util/Map;)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBuilder;->setSize(I)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapKeys;-><init>(Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;I)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapKeys;->getSize()I
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapKeys;->iterator()Ljava/util/Iterator;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapKeysIterator;-><init>(Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;I)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;-><clinit>()V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;-><init>(II[Ljava/lang/Object;L_COROUTINE/ArtificialStackFrames;)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->bufferMoveEntryToNode(IIILjava/lang/Object;Ljava/lang/Object;IL_COROUTINE/ArtificialStackFrames;)[Ljava/lang/Object;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->containsKey(IILjava/lang/Object;)Z
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->elementsIdentityEquals(Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;)Z
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->entryKeyIndex$runtime_release(I)I
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->get(IILjava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->hasEntryAt$runtime_release(I)Z
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->hasNodeAt(I)Z
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->makeNode(ILjava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;Ljava/lang/Object;IL_COROUTINE/ArtificialStackFrames;)Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->mutablePut(ILjava/lang/Object;Ljava/lang/Object;ILandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBuilder;)Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->mutablePutAll(Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;ILandroidx/compose/runtime/external/kotlinx/collections/immutable/internal/DeltaCounter;Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBuilder;)Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->nodeAtIndex$runtime_release(I)Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->nodeIndex$runtime_release(I)I
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->put(IILjava/lang/Object;Ljava/lang/Object;)Landroidx/compose/ui/input/pointer/util/PointerIdArray;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->valueAtKeyIndex(I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNodeBaseIterator;-><init>()V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNodeBaseIterator;->reset(II[Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNodeKeysIterator;-><init>(I)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNodeKeysIterator;->next()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/persistentOrderedSet/Links;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/persistentOrderedSet/PersistentOrderedSet;-><clinit>()V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/persistentOrderedSet/PersistentOrderedSet;-><init>(Ljava/lang/Object;Ljava/lang/Object;Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;)V
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/persistentOrderedSet/PersistentOrderedSet;->getSize()I
+HSPLandroidx/compose/runtime/external/kotlinx/collections/immutable/internal/DeltaCounter;-><init>()V
+HSPLandroidx/compose/runtime/internal/ComposableLambdaImpl;-><init>(IZ)V
+HSPLandroidx/compose/runtime/internal/ComposableLambdaImpl;->invoke(Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/internal/ComposableLambdaImpl;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/internal/ComposableLambdaImpl;->invoke(Ljava/lang/Object;Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/internal/ComposableLambdaImpl;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/internal/ComposableLambdaImpl;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/internal/ComposableLambdaImpl;->update(Lkotlin/jvm/internal/Lambda;)V
+HSPLandroidx/compose/runtime/internal/PersistentCompositionLocalHashMap$Builder;-><init>(Landroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;)V
+HSPLandroidx/compose/runtime/internal/PersistentCompositionLocalHashMap$Builder;->build()Landroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;
+HSPLandroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;-><clinit>()V
+HSPLandroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;->containsKey(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;->putValue(Landroidx/compose/runtime/CompositionLocal;Landroidx/compose/runtime/State;)Landroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;
+HSPLandroidx/compose/runtime/internal/ThreadMap;-><init>(I[J[Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/internal/ThreadMap;->find(J)I
+HSPLandroidx/compose/runtime/internal/ThreadMap;->newWith(JLjava/lang/Object;)Landroidx/compose/runtime/internal/ThreadMap;
+HSPLandroidx/compose/runtime/saveable/ListSaverKt$listSaver$1;-><init>(Lkotlin/coroutines/CoroutineContext$plus$1;)V
+HSPLandroidx/compose/runtime/saveable/ListSaverKt$listSaver$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/saveable/RememberSaveableKt$rememberSaveable$1;-><init>(Landroidx/compose/runtime/saveable/SaveableHolder;Landroidx/compose/runtime/saveable/SaverKt$Saver$1;Landroidx/compose/runtime/saveable/SaveableStateRegistry;Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/saveable/RememberSaveableKt$rememberSaveable$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/saveable/SaveableHolder;-><init>(Landroidx/compose/runtime/saveable/SaverKt$Saver$1;Landroidx/compose/runtime/saveable/SaveableStateRegistry;Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/saveable/SaveableHolder;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/runtime/saveable/SaveableHolder;->onRemembered()V
+HSPLandroidx/compose/runtime/saveable/SaveableHolder;->register()V
+HSPLandroidx/compose/runtime/saveable/SaveableStateHolderImpl$RegistryHolder$registry$1;-><init>(Landroidx/compose/runtime/saveable/SaveableStateHolderImpl;)V
+HSPLandroidx/compose/runtime/saveable/SaveableStateHolderImpl$RegistryHolder;-><init>(Landroidx/compose/runtime/saveable/SaveableStateHolderImpl;Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/saveable/SaveableStateHolderImpl$SaveableStateProvider$1$1$invoke$$inlined$onDispose$1;-><init>(Landroidx/compose/runtime/saveable/SaveableStateHolderImpl$RegistryHolder;Landroidx/compose/runtime/saveable/SaveableStateHolderImpl;Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/saveable/SaveableStateHolderImpl$SaveableStateProvider$1$1$invoke$$inlined$onDispose$1;->dispose()V
+HSPLandroidx/compose/runtime/saveable/SaveableStateHolderImpl;-><clinit>()V
+HSPLandroidx/compose/runtime/saveable/SaveableStateHolderImpl;-><init>(Ljava/util/Map;)V
+HSPLandroidx/compose/runtime/saveable/SaveableStateHolderImpl;->SaveableStateProvider(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/runtime/saveable/SaveableStateRegistryImpl$registerProvider$3;-><init>(Landroidx/compose/runtime/saveable/SaveableStateRegistryImpl;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/runtime/saveable/SaveableStateRegistryImpl;-><init>(Ljava/util/Map;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/runtime/saveable/SaveableStateRegistryImpl;->canBeSaved(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/saveable/SaveableStateRegistryImpl;->consumeRestored(Ljava/lang/String;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/saveable/SaveableStateRegistryImpl;->performSave()Ljava/util/Map;
+HSPLandroidx/compose/runtime/saveable/SaveableStateRegistryImpl;->registerProvider(Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Landroidx/compose/runtime/saveable/SaveableStateRegistryImpl$registerProvider$3;
+HSPLandroidx/compose/runtime/saveable/SaveableStateRegistryKt;-><clinit>()V
+HSPLandroidx/compose/runtime/saveable/SaverKt$Saver$1;-><init>(Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/runtime/saveable/SaverKt;-><clinit>()V
+HSPLandroidx/compose/runtime/snapshots/GlobalSnapshot$1$1$1;-><init>(ILjava/util/List;)V
+HSPLandroidx/compose/runtime/snapshots/GlobalSnapshot$1$1$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/snapshots/GlobalSnapshot;-><init>(ILandroidx/compose/runtime/snapshots/SnapshotIdSet;)V
+HSPLandroidx/compose/runtime/snapshots/GlobalSnapshot;->dispose()V
+HSPLandroidx/compose/runtime/snapshots/GlobalSnapshot;->notifyObjectsInitialized$runtime_release()V
+HSPLandroidx/compose/runtime/snapshots/GlobalSnapshot;->takeNestedMutableSnapshot(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Landroidx/compose/runtime/snapshots/MutableSnapshot;
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;-><clinit>()V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;-><init>(ILandroidx/compose/runtime/snapshots/SnapshotIdSet;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->advance$runtime_release()V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->apply()Lokhttp3/MediaType;
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->closeLocked$runtime_release()V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->dispose()V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->getModified$runtime_release()Landroidx/compose/runtime/collection/IdentityArraySet;
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->getReadObserver$runtime_release()Lkotlin/jvm/functions/Function1;
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->getReadOnly()Z
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->getWriteCount$runtime_release()I
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->getWriteObserver$runtime_release()Lkotlin/jvm/functions/Function1;
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->innerApplyLocked$runtime_release(ILjava/util/HashMap;Landroidx/compose/runtime/snapshots/SnapshotIdSet;)Lokhttp3/MediaType;
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->nestedDeactivated$runtime_release()V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->notifyObjectsInitialized$runtime_release()V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->recordModified$runtime_release(Landroidx/compose/runtime/snapshots/StateObject;)V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->recordPrevious$runtime_release(I)V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->releasePinnedSnapshotsForCloseLocked$runtime_release()V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->setModified(Landroidx/compose/runtime/collection/IdentityArraySet;)V
+HSPLandroidx/compose/runtime/snapshots/MutableSnapshot;->setWriteCount$runtime_release(I)V
+HSPLandroidx/compose/runtime/snapshots/Snapshot$Companion$$ExternalSyntheticLambda0;-><init>(Lkotlin/jvm/internal/Lambda;I)V
+HSPLandroidx/compose/runtime/snapshots/Snapshot;-><init>(ILandroidx/compose/runtime/snapshots/SnapshotIdSet;)V
+HSPLandroidx/compose/runtime/snapshots/Snapshot;->getId()I
+HSPLandroidx/compose/runtime/snapshots/Snapshot;->getInvalid$runtime_release()Landroidx/compose/runtime/snapshots/SnapshotIdSet;
+HSPLandroidx/compose/runtime/snapshots/Snapshot;->makeCurrent()Landroidx/compose/runtime/snapshots/Snapshot;
+HSPLandroidx/compose/runtime/snapshots/Snapshot;->restoreCurrent(Landroidx/compose/runtime/snapshots/Snapshot;)V
+HSPLandroidx/compose/runtime/snapshots/Snapshot;->setId$runtime_release(I)V
+HSPLandroidx/compose/runtime/snapshots/Snapshot;->setInvalid$runtime_release(Landroidx/compose/runtime/snapshots/SnapshotIdSet;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotApplyResult$Success;-><clinit>()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotDoubleIndexHeap;-><init>()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotDoubleIndexHeap;->add(I)I
+HSPLandroidx/compose/runtime/snapshots/SnapshotDoubleIndexHeap;->swap(II)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotIdSet;-><clinit>()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotIdSet;-><init>(JJI[I)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotIdSet;->andNot(Landroidx/compose/runtime/snapshots/SnapshotIdSet;)Landroidx/compose/runtime/snapshots/SnapshotIdSet;
+HSPLandroidx/compose/runtime/snapshots/SnapshotIdSet;->clear(I)Landroidx/compose/runtime/snapshots/SnapshotIdSet;
+HSPLandroidx/compose/runtime/snapshots/SnapshotIdSet;->get(I)Z
+HSPLandroidx/compose/runtime/snapshots/SnapshotIdSet;->or(Landroidx/compose/runtime/snapshots/SnapshotIdSet;)Landroidx/compose/runtime/snapshots/SnapshotIdSet;
+HSPLandroidx/compose/runtime/snapshots/SnapshotIdSet;->set(I)Landroidx/compose/runtime/snapshots/SnapshotIdSet;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt$mergedReadObserver$1;-><init>(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;I)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt$mergedReadObserver$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt$mergedReadObserver$1;->invoke(Ljava/lang/Object;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;-><clinit>()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->access$advanceGlobalSnapshot()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->access$mergedWriteObserver(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->access$optimisticMerges(Landroidx/compose/runtime/snapshots/MutableSnapshot;Landroidx/compose/runtime/snapshots/MutableSnapshot;Landroidx/compose/runtime/snapshots/SnapshotIdSet;)Ljava/util/HashMap;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->access$validateOpen(Landroidx/compose/runtime/snapshots/Snapshot;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->advanceGlobalSnapshot(Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->checkAndOverwriteUnusedRecordsLocked()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->createTransparentSnapshotWithNoParentReadObserver(Landroidx/compose/runtime/snapshots/Snapshot;Lkotlin/jvm/functions/Function1;Z)Landroidx/compose/runtime/snapshots/Snapshot;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->current(Landroidx/compose/runtime/snapshots/StateRecord;)Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->currentSnapshot()Landroidx/compose/runtime/snapshots/Snapshot;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->mergedReadObserver(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Z)Lkotlin/jvm/functions/Function1;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->newOverwritableRecordLocked(Landroidx/compose/runtime/snapshots/StateRecord;Landroidx/compose/runtime/snapshots/StateObject;)Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->notifyWrite(Landroidx/compose/runtime/snapshots/Snapshot;Landroidx/compose/runtime/snapshots/StateObject;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->overwritableRecord(Landroidx/compose/runtime/snapshots/StateRecord;Landroidx/compose/runtime/snapshots/StateObject;Landroidx/compose/runtime/snapshots/Snapshot;Landroidx/compose/runtime/snapshots/StateRecord;)Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->overwriteUnusedRecordsLocked(Landroidx/compose/runtime/snapshots/StateObject;)Z
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->processForUnusedRecordsLocked(Landroidx/compose/runtime/snapshots/StateObject;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->readable(Landroidx/compose/runtime/snapshots/StateRecord;ILandroidx/compose/runtime/snapshots/SnapshotIdSet;)Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->readable(Landroidx/compose/runtime/snapshots/StateRecord;Landroidx/compose/runtime/snapshots/StateObject;)Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->releasePinningLocked(I)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->takeNewGlobalSnapshot(Landroidx/compose/runtime/snapshots/Snapshot;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/snapshots/SnapshotKt;->writableRecord(Landroidx/compose/runtime/snapshots/StateRecord;Landroidx/compose/runtime/snapshots/StateObject;Landroidx/compose/runtime/snapshots/Snapshot;)Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList$StateListStateRecord;-><init>(Landroidx/compose/runtime/external/kotlinx/collections/immutable/PersistentList;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList$StateListStateRecord;->assign(Landroidx/compose/runtime/snapshots/StateRecord;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList$StateListStateRecord;->create()Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList$addAll$1;-><init>(Landroidx/compose/ui/layout/Placeable;II)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList$addAll$1;->invoke(Landroidx/compose/ui/layout/Placeable$PlacementScope;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList$addAll$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;-><init>()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;->add(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;->get(I)Ljava/lang/Object;
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;->getFirstStateRecord()Landroidx/compose/runtime/snapshots/StateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;->getReadable$runtime_release()Landroidx/compose/runtime/snapshots/SnapshotStateList$StateListStateRecord;
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;->isEmpty()Z
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;->prependStateRecord(Landroidx/compose/runtime/snapshots/StateRecord;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;->remove(Ljava/lang/Object;)Z
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateList;->size()I
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateListKt;-><clinit>()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateObserver$ObservedScopeMap;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateObserver$ObservedScopeMap;->observe(Ljava/lang/Object;Lkotlin/collections/AbstractMap$toString$1;Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateObserver$ObservedScopeMap;->recordInvalidation(Ljava/util/Set;)Z
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateObserver$ObservedScopeMap;->recordRead(Ljava/lang/Object;ILjava/lang/Object;Landroidx/compose/runtime/collection/IdentityArrayIntMap;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateObserver$ObservedScopeMap;->removeScopeIf()V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateObserver;-><init>(Landroidx/compose/ui/platform/AndroidComposeView$focusOwner$1;)V
+HSPLandroidx/compose/runtime/snapshots/SnapshotStateObserver;->access$drainChanges(Landroidx/compose/runtime/snapshots/SnapshotStateObserver;)Z
+HSPLandroidx/compose/runtime/snapshots/StateRecord;-><init>()V
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;-><init>(Landroidx/compose/runtime/snapshots/MutableSnapshot;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ZZ)V
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->apply()Lokhttp3/MediaType;
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->dispose()V
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->getId()I
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->getInvalid$runtime_release()Landroidx/compose/runtime/snapshots/SnapshotIdSet;
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->getReadOnly()Z
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->getWriteCount$runtime_release()I
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->notifyObjectsInitialized$runtime_release()V
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->recordModified$runtime_release(Landroidx/compose/runtime/snapshots/StateObject;)V
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->setWriteCount$runtime_release(I)V
+HSPLandroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;->takeNestedMutableSnapshot(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Landroidx/compose/runtime/snapshots/MutableSnapshot;
+HSPLandroidx/compose/runtime/tooling/InspectionTablesKt;-><clinit>()V
+HSPLandroidx/compose/ui/BiasAlignment$Horizontal;-><init>(F)V
+HSPLandroidx/compose/ui/BiasAlignment$Horizontal;->align(IILandroidx/compose/ui/unit/LayoutDirection;)I
+HSPLandroidx/compose/ui/BiasAlignment$Horizontal;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/BiasAlignment$Vertical;-><init>(F)V
+HSPLandroidx/compose/ui/BiasAlignment$Vertical;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/BiasAlignment;-><init>(FF)V
+HSPLandroidx/compose/ui/BiasAlignment;->align-KFBX0sM(JJLandroidx/compose/ui/unit/LayoutDirection;)J
+HSPLandroidx/compose/ui/BiasAlignment;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/CombinedModifier$toString$1;-><clinit>()V
+HSPLandroidx/compose/ui/CombinedModifier$toString$1;-><init>(I)V
+HSPLandroidx/compose/ui/CombinedModifier$toString$1;->invoke(Landroidx/compose/ui/layout/Measurable;I)Ljava/lang/Integer;
+HSPLandroidx/compose/ui/CombinedModifier$toString$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/CombinedModifier;-><init>(Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;)V
+HSPLandroidx/compose/ui/CombinedModifier;->all(Lkotlin/jvm/functions/Function1;)Z
+HSPLandroidx/compose/ui/CombinedModifier;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/CombinedModifier;->foldIn(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/ComposedModifier;-><init>(Lkotlin/jvm/functions/Function3;)V
+HSPLandroidx/compose/ui/Modifier$Companion;-><clinit>()V
+HSPLandroidx/compose/ui/Modifier$Companion;->all(Lkotlin/jvm/functions/Function1;)Z
+HSPLandroidx/compose/ui/Modifier$Companion;->then(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/Modifier$Element;->all(Lkotlin/jvm/functions/Function1;)Z
+HSPLandroidx/compose/ui/Modifier$Element;->foldIn(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/Modifier$Node;-><init>()V
+HSPLandroidx/compose/ui/Modifier$Node;->getCoroutineScope()Lkotlinx/coroutines/CoroutineScope;
+HSPLandroidx/compose/ui/Modifier$Node;->getShouldAutoInvalidate()Z
+HSPLandroidx/compose/ui/Modifier$Node;->markAsAttached$ui_release()V
+HSPLandroidx/compose/ui/Modifier$Node;->markAsDetached$ui_release()V
+HSPLandroidx/compose/ui/Modifier$Node;->onAttach()V
+HSPLandroidx/compose/ui/Modifier$Node;->onDetach()V
+HSPLandroidx/compose/ui/Modifier$Node;->onReset()V
+HSPLandroidx/compose/ui/Modifier$Node;->reset$ui_release()V
+HSPLandroidx/compose/ui/Modifier$Node;->runAttachLifecycle$ui_release()V
+HSPLandroidx/compose/ui/Modifier$Node;->runDetachLifecycle$ui_release()V
+HSPLandroidx/compose/ui/Modifier$Node;->updateCoordinator$ui_release(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/ui/Modifier;->then(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/MotionDurationScale;->getKey()Lkotlin/coroutines/CoroutineContext$Key;
+HSPLandroidx/compose/ui/ZIndexElement;-><init>()V
+HSPLandroidx/compose/ui/ZIndexElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/ZIndexElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/ZIndexNode$measure$1;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/ZIndexNode$measure$1;->invoke(Landroidx/compose/ui/layout/Placeable$PlacementScope;)V
+HSPLandroidx/compose/ui/ZIndexNode$measure$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/ZIndexNode$measure$1;->invoke(Ljava/lang/Throwable;)V
+HSPLandroidx/compose/ui/ZIndexNode;-><init>(F)V
+HSPLandroidx/compose/ui/ZIndexNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/autofill/AndroidAutofill;-><init>(Landroid/view/View;Landroidx/compose/ui/autofill/AutofillTree;)V
+HSPLandroidx/compose/ui/autofill/AutofillCallback;-><clinit>()V
+HSPLandroidx/compose/ui/autofill/AutofillCallback;->register(Landroidx/compose/ui/autofill/AndroidAutofill;)V
+HSPLandroidx/compose/ui/autofill/AutofillTree;-><init>()V
+HSPLandroidx/compose/ui/draw/CacheDrawModifierNodeImpl;-><init>(Landroidx/compose/ui/draw/CacheDrawScope;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/draw/CacheDrawModifierNodeImpl;->draw(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/compose/ui/draw/CacheDrawModifierNodeImpl;->getDensity()Landroidx/compose/ui/unit/Density;
+HSPLandroidx/compose/ui/draw/CacheDrawModifierNodeImpl;->getLayoutDirection()Landroidx/compose/ui/unit/LayoutDirection;
+HSPLandroidx/compose/ui/draw/CacheDrawModifierNodeImpl;->getSize-NH-jbRc()J
+HSPLandroidx/compose/ui/draw/CacheDrawModifierNodeImpl;->invalidateDrawCache()V
+HSPLandroidx/compose/ui/draw/CacheDrawModifierNodeImpl;->onMeasureResultChanged()V
+HSPLandroidx/compose/ui/draw/CacheDrawScope$onDrawBehind$1;-><init>(ILkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/draw/CacheDrawScope$onDrawBehind$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/draw/CacheDrawScope;-><init>()V
+HSPLandroidx/compose/ui/draw/CacheDrawScope;->getDensity()F
+HSPLandroidx/compose/ui/draw/CacheDrawScope;->getSize-NH-jbRc()J
+HSPLandroidx/compose/ui/draw/CacheDrawScope;->onDrawWithContent(Lkotlin/jvm/functions/Function1;)Landroidx/compose/ui/draw/DrawResult;
+HSPLandroidx/compose/ui/draw/ClipKt;->clip(Landroidx/compose/ui/Modifier;Landroidx/compose/ui/graphics/Shape;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/draw/ClipKt;->clipToBounds(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/draw/ClipKt;->drawWithCache(Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/draw/ClipKt;->paint$default(Landroidx/compose/ui/Modifier;Landroidx/compose/ui/graphics/painter/Painter;Landroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;FLandroidx/compose/ui/graphics/BlendModeColorFilter;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/draw/DrawResult;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/draw/DrawWithCacheElement;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/draw/DrawWithCacheElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/draw/DrawWithCacheElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/draw/EmptyBuildDrawCacheParams;-><clinit>()V
+HSPLandroidx/compose/ui/draw/PainterElement;-><init>(Landroidx/compose/ui/graphics/painter/Painter;ZLandroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;FLandroidx/compose/ui/graphics/BlendModeColorFilter;)V
+HSPLandroidx/compose/ui/draw/PainterElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/draw/PainterElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/draw/PainterElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/ui/draw/PainterNode$measure$1;-><init>(Landroidx/compose/ui/layout/Placeable;I)V
+HSPLandroidx/compose/ui/draw/PainterNode$measure$1;->invoke(Landroidx/compose/ui/layout/Placeable$PlacementScope;)V
+HSPLandroidx/compose/ui/draw/PainterNode$measure$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/draw/PainterNode;-><init>(Landroidx/compose/ui/graphics/painter/Painter;ZLandroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;FLandroidx/compose/ui/graphics/BlendModeColorFilter;)V
+HSPLandroidx/compose/ui/draw/PainterNode;->draw(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/compose/ui/draw/PainterNode;->getUseIntrinsicSize()Z
+HSPLandroidx/compose/ui/draw/PainterNode;->hasSpecifiedAndFiniteHeight-uvyYCjk(J)Z
+HSPLandroidx/compose/ui/draw/PainterNode;->hasSpecifiedAndFiniteWidth-uvyYCjk(J)Z
+HSPLandroidx/compose/ui/draw/PainterNode;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/draw/PainterNode;->modifyConstraints-ZezNO4M(J)J
+HSPLandroidx/compose/ui/focus/FocusChangedElement;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/focus/FocusChangedElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/focus/FocusChangedElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/focus/FocusChangedElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/ui/focus/FocusChangedNode;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/focus/FocusChangedNode;->onFocusEvent(Landroidx/compose/ui/focus/FocusStateImpl;)V
+HSPLandroidx/compose/ui/focus/FocusDirection;-><init>(I)V
+HSPLandroidx/compose/ui/focus/FocusInvalidationManager;-><init>(Landroidx/compose/ui/platform/AndroidComposeView$focusOwner$1;)V
+HSPLandroidx/compose/ui/focus/FocusInvalidationManager;->scheduleInvalidation(Ljava/util/LinkedHashSet;Ljava/lang/Object;)V
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->beamBeats-I7lrPNg(Landroidx/compose/ui/geometry/Rect;Landroidx/compose/ui/geometry/Rect;Landroidx/compose/ui/geometry/Rect;I)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->beamBeats_I7lrPNg$inSourceBeam(ILandroidx/compose/ui/geometry/Rect;Landroidx/compose/ui/geometry/Rect;)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->clearFocus(Landroidx/compose/ui/focus/FocusTargetNode;ZZ)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->collectAccessibleChildren(Landroidx/compose/ui/node/DelegatableNode;Landroidx/compose/runtime/collection/MutableVector;)V
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->findActiveFocusNode(Landroidx/compose/ui/focus/FocusTargetNode;)Landroidx/compose/ui/focus/FocusTargetNode;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->findBestCandidate-4WY_MpI(Landroidx/compose/runtime/collection/MutableVector;Landroidx/compose/ui/geometry/Rect;I)Landroidx/compose/ui/focus/FocusTargetNode;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->findChildCorrespondingToFocusEnter--OM-vw8(Landroidx/compose/ui/focus/FocusTargetNode;ILkotlin/jvm/functions/Function1;)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->focusRect(Landroidx/compose/ui/focus/FocusTargetNode;)Landroidx/compose/ui/geometry/Rect;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->generateAndSearchChildren-4C6V_qg$1(Landroidx/compose/ui/focus/FocusTargetNode;Landroidx/compose/ui/focus/FocusTargetNode;ILkotlin/jvm/functions/Function1;)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->getActiveChild(Landroidx/compose/ui/focus/FocusTargetNode;)Landroidx/compose/ui/focus/FocusTargetNode;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->getFocusState(Landroidx/compose/ui/focus/FocusEventModifierNode;)Landroidx/compose/ui/focus/FocusStateImpl;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->grantFocus(Landroidx/compose/ui/focus/FocusTargetNode;)V
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->invalidateFocusEvent(Landroidx/compose/ui/focus/FocusEventModifierNode;)V
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->isBetterCandidate_I7lrPNg$isCandidate(ILandroidx/compose/ui/geometry/Rect;Landroidx/compose/ui/geometry/Rect;)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->isBetterCandidate_I7lrPNg$weightedDistance(ILandroidx/compose/ui/geometry/Rect;Landroidx/compose/ui/geometry/Rect;)J
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->isEligibleForFocusSearch(Landroidx/compose/ui/focus/FocusTargetNode;)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->onFocusChanged(Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->performCustomClearFocus-Mxy_nc0(Landroidx/compose/ui/focus/FocusTargetNode;I)I
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->performCustomEnter-Mxy_nc0(Landroidx/compose/ui/focus/FocusTargetNode;I)I
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->performCustomRequestFocus-Mxy_nc0(Landroidx/compose/ui/focus/FocusTargetNode;I)I
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->performRequestFocus(Landroidx/compose/ui/focus/FocusTargetNode;)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->refreshFocusEventNodes(Landroidx/compose/ui/focus/FocusTargetNode;)V
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->requestFocusForChild(Landroidx/compose/ui/focus/FocusTargetNode;Landroidx/compose/ui/focus/FocusTargetNode;)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->requireActiveChild(Landroidx/compose/ui/focus/FocusTargetNode;)Landroidx/compose/ui/focus/FocusTargetNode;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->requireTransactionManager(Landroidx/compose/ui/focus/FocusTargetNode;)Lcom/google/gson/internal/ConstructorConstructor;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->searchBeyondBounds--OM-vw8(Landroidx/compose/ui/focus/FocusTargetNode;ILandroidx/compose/ui/focus/FocusOwnerImpl$moveFocus$foundNextItem$1;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->searchChildren-4C6V_qg$1(Landroidx/compose/ui/focus/FocusTargetNode;Landroidx/compose/ui/focus/FocusTargetNode;ILkotlin/jvm/functions/Function1;)Z
+HSPLandroidx/compose/ui/focus/FocusModifierKt;->twoDimensionalFocusSearch--OM-vw8(Landroidx/compose/ui/focus/FocusTargetNode;ILandroidx/compose/ui/focus/FocusOwnerImpl$moveFocus$foundNextItem$1;)Ljava/lang/Boolean;
+HSPLandroidx/compose/ui/focus/FocusOwnerImpl$modifier$1;-><init>(Landroidx/compose/ui/focus/FocusOwnerImpl;)V
+HSPLandroidx/compose/ui/focus/FocusOwnerImpl$modifier$1;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/focus/FocusOwnerImpl$moveFocus$foundNextItem$1;-><init>(Landroidx/compose/ui/focus/FocusTargetNode;Ljava/lang/Object;ILjava/lang/Object;I)V
+HSPLandroidx/compose/ui/focus/FocusOwnerImpl$moveFocus$foundNextItem$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/focus/FocusOwnerImpl;-><init>(Landroidx/compose/ui/platform/AndroidComposeView$focusOwner$1;)V
+HSPLandroidx/compose/ui/focus/FocusOwnerImpl;->moveFocus-3ESFkO8(I)Z
+HSPLandroidx/compose/ui/focus/FocusProperties$exit$1;-><clinit>()V
+HSPLandroidx/compose/ui/focus/FocusProperties$exit$1;-><init>(I)V
+HSPLandroidx/compose/ui/focus/FocusProperties$exit$1;->invoke(Landroidx/compose/ui/node/AlignmentLinesOwner;)V
+HSPLandroidx/compose/ui/focus/FocusProperties$exit$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/focus/FocusProperties$exit$1;->invoke-3ESFkO8()Landroidx/compose/ui/focus/FocusRequester;
+HSPLandroidx/compose/ui/focus/FocusPropertiesImpl;-><init>()V
+HSPLandroidx/compose/ui/focus/FocusPropertiesImpl;->setCanFocus(Z)V
+HSPLandroidx/compose/ui/focus/FocusRequester;-><clinit>()V
+HSPLandroidx/compose/ui/focus/FocusRequester;-><init>()V
+HSPLandroidx/compose/ui/focus/FocusStateImpl;-><clinit>()V
+HSPLandroidx/compose/ui/focus/FocusStateImpl;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/ui/focus/FocusStateImpl;->isFocused()Z
+HSPLandroidx/compose/ui/focus/FocusTargetNode$FocusTargetElement;-><clinit>()V
+HSPLandroidx/compose/ui/focus/FocusTargetNode$FocusTargetElement;-><init>()V
+HSPLandroidx/compose/ui/focus/FocusTargetNode$FocusTargetElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/focus/FocusTargetNode$FocusTargetElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/focus/FocusTargetNode;-><init>()V
+HSPLandroidx/compose/ui/focus/FocusTargetNode;->fetchFocusProperties$ui_release()Landroidx/compose/ui/focus/FocusPropertiesImpl;
+HSPLandroidx/compose/ui/focus/FocusTargetNode;->getFocusState()Landroidx/compose/ui/focus/FocusStateImpl;
+HSPLandroidx/compose/ui/focus/FocusTargetNode;->invalidateFocus$ui_release()V
+HSPLandroidx/compose/ui/focus/FocusTargetNode;->onReset()V
+HSPLandroidx/compose/ui/focus/FocusTargetNode;->scheduleInvalidationForFocusEvents$ui_release()V
+HSPLandroidx/compose/ui/focus/FocusTargetNode;->setFocusState(Landroidx/compose/ui/focus/FocusStateImpl;)V
+HSPLandroidx/compose/ui/geometry/CornerRadius;-><clinit>()V
+HSPLandroidx/compose/ui/geometry/CornerRadius;->getX-impl(J)F
+HSPLandroidx/compose/ui/geometry/CornerRadius;->getY-impl(J)F
+HSPLandroidx/compose/ui/geometry/MutableRect;-><init>()V
+HSPLandroidx/compose/ui/geometry/MutableRect;->isEmpty()Z
+HSPLandroidx/compose/ui/geometry/Offset;-><clinit>()V
+HSPLandroidx/compose/ui/geometry/Offset;->getX-impl(J)F
+HSPLandroidx/compose/ui/geometry/Offset;->getY-impl(J)F
+HSPLandroidx/compose/ui/geometry/Rect;-><clinit>()V
+HSPLandroidx/compose/ui/geometry/Rect;-><init>(FFFF)V
+HSPLandroidx/compose/ui/geometry/Rect;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/geometry/Rect;->intersect(Landroidx/compose/ui/geometry/Rect;)Landroidx/compose/ui/geometry/Rect;
+HSPLandroidx/compose/ui/geometry/Rect;->translate(FF)Landroidx/compose/ui/geometry/Rect;
+HSPLandroidx/compose/ui/geometry/Rect;->translate-k-4lQ0M(J)Landroidx/compose/ui/geometry/Rect;
+HSPLandroidx/compose/ui/geometry/RoundRect;-><clinit>()V
+HSPLandroidx/compose/ui/geometry/RoundRect;-><init>(FFFFJJJJ)V
+HSPLandroidx/compose/ui/geometry/Size;-><clinit>()V
+HSPLandroidx/compose/ui/geometry/Size;-><init>(J)V
+HSPLandroidx/compose/ui/geometry/Size;->equals-impl0(JJ)Z
+HSPLandroidx/compose/ui/geometry/Size;->getHeight-impl(J)F
+HSPLandroidx/compose/ui/geometry/Size;->getMinDimension-impl(J)F
+HSPLandroidx/compose/ui/geometry/Size;->getWidth-impl(J)F
+HSPLandroidx/compose/ui/geometry/Size;->isEmpty-impl(J)Z
+HSPLandroidx/compose/ui/graphics/AndroidCanvas;-><init>()V
+HSPLandroidx/compose/ui/graphics/AndroidCanvas;->drawImageRect-HPBpro0(Landroidx/compose/ui/graphics/ImageBitmap;JJJJLandroidx/compose/ui/graphics/AndroidPaint;)V
+HSPLandroidx/compose/ui/graphics/AndroidCanvas;->drawRect(FFFFLandroidx/compose/ui/graphics/AndroidPaint;)V
+HSPLandroidx/compose/ui/graphics/AndroidCanvas;->drawRoundRect(FFFFFFLandroidx/compose/ui/graphics/AndroidPaint;)V
+HSPLandroidx/compose/ui/graphics/AndroidCanvas;->restore()V
+HSPLandroidx/compose/ui/graphics/AndroidCanvas;->save()V
+HSPLandroidx/compose/ui/graphics/AndroidCanvas;->scale(FF)V
+HSPLandroidx/compose/ui/graphics/AndroidCanvas;->translate(FF)V
+HSPLandroidx/compose/ui/graphics/AndroidCanvas_androidKt;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/AndroidImageBitmap;-><init>(Landroid/graphics/Bitmap;)V
+HSPLandroidx/compose/ui/graphics/AndroidPaint;-><init>(Landroid/graphics/Paint;)V
+HSPLandroidx/compose/ui/graphics/AndroidPaint;->setAlpha(F)V
+HSPLandroidx/compose/ui/graphics/AndroidPaint;->setBlendMode-s9anfk8(I)V
+HSPLandroidx/compose/ui/graphics/AndroidPaint;->setColor-8_81llA(J)V
+HSPLandroidx/compose/ui/graphics/AndroidPaint;->setStyle-k9PVt8s(I)V
+HSPLandroidx/compose/ui/graphics/AndroidPaint_androidKt$WhenMappings;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/BlockGraphicsLayerElement;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/graphics/BlockGraphicsLayerElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/graphics/BlockGraphicsLayerElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/BlockGraphicsLayerModifier;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/graphics/BlockGraphicsLayerModifier;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/graphics/Brush;-><init>()V
+HSPLandroidx/compose/ui/graphics/BrushKt;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/BrushKt;->Color$default(III)J
+HSPLandroidx/compose/ui/graphics/BrushKt;->Color(FFFFLandroidx/compose/ui/graphics/colorspace/ColorSpace;)J
+HSPLandroidx/compose/ui/graphics/BrushKt;->Color(I)J
+HSPLandroidx/compose/ui/graphics/BrushKt;->Color(J)J
+HSPLandroidx/compose/ui/graphics/BrushKt;->Paint()Landroidx/compose/ui/graphics/AndroidPaint;
+HSPLandroidx/compose/ui/graphics/BrushKt;->drawOutline-wDX37Ww$default(Landroidx/compose/ui/graphics/drawscope/DrawScope;Landroidx/compose/ui/graphics/BrushKt;J)V
+HSPLandroidx/compose/ui/graphics/BrushKt;->graphicsLayer(Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/graphics/BrushKt;->graphicsLayer-Ap8cVGQ$default(Landroidx/compose/ui/Modifier;FFLandroidx/compose/ui/graphics/Shape;ZI)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/graphics/BrushKt;->setFrom-tU-YjHk(Landroid/graphics/Matrix;[F)V
+HSPLandroidx/compose/ui/graphics/BrushKt;->toArgb-8_81llA(J)I
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$1(Landroid/graphics/RenderNode;)I
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$1(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$1(Landroid/graphics/RenderNode;I)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$2(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$2(Landroid/graphics/RenderNode;I)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$3(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$4(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$5(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$6(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$7(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m$8(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/Canvas;Landroid/graphics/RenderNode;)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)I
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)Z
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;I)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;Z)V
+HSPLandroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;->m(Landroid/view/View;)V
+HSPLandroidx/compose/ui/graphics/Color;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/Color;-><init>(J)V
+HSPLandroidx/compose/ui/graphics/Color;->convert-vNxB06k(JLandroidx/compose/ui/graphics/colorspace/ColorSpace;)J
+HSPLandroidx/compose/ui/graphics/Color;->copy-wmQWz5c$default(JF)J
+HSPLandroidx/compose/ui/graphics/Color;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/Color;->equals-impl0(JJ)Z
+HSPLandroidx/compose/ui/graphics/Color;->getAlpha-impl(J)F
+HSPLandroidx/compose/ui/graphics/Color;->getBlue-impl(J)F
+HSPLandroidx/compose/ui/graphics/Color;->getColorSpace-impl(J)Landroidx/compose/ui/graphics/colorspace/ColorSpace;
+HSPLandroidx/compose/ui/graphics/Color;->getGreen-impl(J)F
+HSPLandroidx/compose/ui/graphics/Color;->getRed-impl(J)F
+HSPLandroidx/compose/ui/graphics/ColorSpaceVerificationHelper$$ExternalSyntheticLambda1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/graphics/Float16;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/Float16;->constructor-impl(F)S
+HSPLandroidx/compose/ui/graphics/Float16;->toFloat-impl(S)F
+HSPLandroidx/compose/ui/graphics/GraphicsLayerElement;-><init>(FFFFFFFFFFJLandroidx/compose/ui/graphics/Shape;ZJJI)V
+HSPLandroidx/compose/ui/graphics/GraphicsLayerElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/graphics/GraphicsLayerElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/GraphicsLayerElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/ui/graphics/GraphicsLayerScopeKt;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/Matrix;->constructor-impl$default()[F
+HSPLandroidx/compose/ui/graphics/Matrix;->map-MK-Hz9U([FJ)J
+HSPLandroidx/compose/ui/graphics/Matrix;->map-impl([FLandroidx/compose/ui/geometry/MutableRect;)V
+HSPLandroidx/compose/ui/graphics/Outline$Rectangle;-><init>(Landroidx/compose/ui/geometry/Rect;)V
+HSPLandroidx/compose/ui/graphics/Outline$Rounded;-><init>(Landroidx/compose/ui/geometry/RoundRect;)V
+HSPLandroidx/compose/ui/graphics/RectangleShapeKt$RectangleShape$1;-><init>(I)V
+HSPLandroidx/compose/ui/graphics/RectangleShapeKt$RectangleShape$1;->createOutline-Pq9zytI(JLandroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/ui/unit/Density;)Landroidx/compose/ui/graphics/BrushKt;
+HSPLandroidx/compose/ui/graphics/ReusableGraphicsLayerScope;-><init>()V
+HSPLandroidx/compose/ui/graphics/Shadow;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/Shadow;-><init>(JJF)V
+HSPLandroidx/compose/ui/graphics/Shadow;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/SimpleGraphicsLayerModifier$layerBlock$1;-><init>(Landroidx/compose/ui/graphics/SimpleGraphicsLayerModifier;)V
+HSPLandroidx/compose/ui/graphics/SimpleGraphicsLayerModifier$layerBlock$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/graphics/SimpleGraphicsLayerModifier;-><init>(FFFFFFFFFFJLandroidx/compose/ui/graphics/Shape;ZJJI)V
+HSPLandroidx/compose/ui/graphics/SimpleGraphicsLayerModifier;->getShouldAutoInvalidate()Z
+HSPLandroidx/compose/ui/graphics/SimpleGraphicsLayerModifier;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/graphics/SolidColor;-><init>(J)V
+HSPLandroidx/compose/ui/graphics/SolidColor;->applyTo-Pq9zytI(FJLandroidx/compose/ui/graphics/AndroidPaint;)V
+HSPLandroidx/compose/ui/graphics/SolidColor;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/TransformOrigin;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/colorspace/Adaptation;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/colorspace/Adaptation;-><init>([F)V
+HSPLandroidx/compose/ui/graphics/colorspace/ColorModel;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/colorspace/ColorModel;->equals-impl0(JJ)Z
+HSPLandroidx/compose/ui/graphics/colorspace/ColorSpace;-><init>(Ljava/lang/String;JI)V
+HSPLandroidx/compose/ui/graphics/colorspace/ColorSpace;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/colorspace/ColorSpace;->isSrgb()Z
+HSPLandroidx/compose/ui/graphics/colorspace/ColorSpaces;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/colorspace/Connector$Companion$identity$1;-><init>(Landroidx/compose/ui/graphics/colorspace/ColorSpace;)V
+HSPLandroidx/compose/ui/graphics/colorspace/Connector;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/colorspace/Connector;-><init>(Landroidx/compose/ui/graphics/colorspace/ColorSpace;Landroidx/compose/ui/graphics/colorspace/ColorSpace;I)V
+HSPLandroidx/compose/ui/graphics/colorspace/Connector;-><init>(Landroidx/compose/ui/graphics/colorspace/ColorSpace;Landroidx/compose/ui/graphics/colorspace/ColorSpace;Landroidx/compose/ui/graphics/colorspace/ColorSpace;[F)V
+HSPLandroidx/compose/ui/graphics/colorspace/Connector;->transformToColor-wmQWz5c$ui_graphics_release(FFFF)J
+HSPLandroidx/compose/ui/graphics/colorspace/Lab;-><init>()V
+HSPLandroidx/compose/ui/graphics/colorspace/Oklab;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/colorspace/Oklab;-><init>()V
+HSPLandroidx/compose/ui/graphics/colorspace/Oklab;->getMaxValue(I)F
+HSPLandroidx/compose/ui/graphics/colorspace/Oklab;->getMinValue(I)F
+HSPLandroidx/compose/ui/graphics/colorspace/Oklab;->toXy$ui_graphics_release(FFF)J
+HSPLandroidx/compose/ui/graphics/colorspace/Oklab;->toZ$ui_graphics_release(FFF)F
+HSPLandroidx/compose/ui/graphics/colorspace/Oklab;->xyzaToColor-JlNiLsg$ui_graphics_release(FFFFLandroidx/compose/ui/graphics/colorspace/ColorSpace;)J
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb$$ExternalSyntheticLambda0;-><init>(Landroidx/compose/ui/graphics/colorspace/Rgb;I)V
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb$$ExternalSyntheticLambda0;->invoke(D)D
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb$$ExternalSyntheticLambda1;-><init>(Landroidx/compose/ui/graphics/colorspace/TransferParameters;I)V
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb$$ExternalSyntheticLambda1;->invoke(D)D
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb$$ExternalSyntheticLambda2;-><init>(DI)V
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb$eotf$1;-><init>(Landroidx/compose/ui/graphics/colorspace/Rgb;I)V
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;-><init>(Ljava/lang/String;[FLandroidx/compose/ui/graphics/colorspace/WhitePoint;DFFI)V
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;-><init>(Ljava/lang/String;[FLandroidx/compose/ui/graphics/colorspace/WhitePoint;Landroidx/compose/ui/graphics/colorspace/TransferParameters;I)V
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;-><init>(Ljava/lang/String;[FLandroidx/compose/ui/graphics/colorspace/WhitePoint;[FLandroidx/compose/ui/graphics/colorspace/DoubleFunction;Landroidx/compose/ui/graphics/colorspace/DoubleFunction;FFLandroidx/compose/ui/graphics/colorspace/TransferParameters;I)V
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;->getMaxValue(I)F
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;->getMinValue(I)F
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;->isSrgb()Z
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;->toXy$ui_graphics_release(FFF)J
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;->toZ$ui_graphics_release(FFF)F
+HSPLandroidx/compose/ui/graphics/colorspace/Rgb;->xyzaToColor-JlNiLsg$ui_graphics_release(FFFFLandroidx/compose/ui/graphics/colorspace/ColorSpace;)J
+HSPLandroidx/compose/ui/graphics/colorspace/TransferParameters;-><init>(DDDDD)V
+HSPLandroidx/compose/ui/graphics/colorspace/TransferParameters;-><init>(DDDDDDD)V
+HSPLandroidx/compose/ui/graphics/colorspace/WhitePoint;-><init>(FF)V
+HSPLandroidx/compose/ui/graphics/colorspace/WhitePoint;->toXyz$ui_graphics_release()[F
+HSPLandroidx/compose/ui/graphics/colorspace/Xyz;-><init>()V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope$DrawParams;-><init>()V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope$drawContext$1;-><init>(Landroidx/compose/ui/graphics/drawscope/CanvasDrawScope;)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope$drawContext$1;->getCanvas()Landroidx/compose/ui/graphics/Canvas;
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope$drawContext$1;->getSize-NH-jbRc()J
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope$drawContext$1;->setSize-uvyYCjk(J)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;-><init>()V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->configurePaint-2qPWKa0$default(Landroidx/compose/ui/graphics/drawscope/CanvasDrawScope;JLkotlin/ResultKt;FLandroidx/compose/ui/graphics/BlendModeColorFilter;I)Landroidx/compose/ui/graphics/AndroidPaint;
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->configurePaint-swdJneE$default(Landroidx/compose/ui/graphics/drawscope/CanvasDrawScope;Landroidx/compose/ui/graphics/Brush;Lkotlin/ResultKt;FLandroidx/compose/ui/graphics/BlendModeColorFilter;I)Landroidx/compose/ui/graphics/AndroidPaint;
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->configurePaint-swdJneE(Landroidx/compose/ui/graphics/Brush;Lkotlin/ResultKt;FLandroidx/compose/ui/graphics/BlendModeColorFilter;II)Landroidx/compose/ui/graphics/AndroidPaint;
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->drawImage-AZ2fEMs(Landroidx/compose/ui/graphics/ImageBitmap;JJJJFLkotlin/ResultKt;Landroidx/compose/ui/graphics/BlendModeColorFilter;II)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->drawRect-AsUm42w(Landroidx/compose/ui/graphics/Brush;JJFLkotlin/ResultKt;Landroidx/compose/ui/graphics/BlendModeColorFilter;I)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->drawRect-n-J9OG0(JJJFLkotlin/ResultKt;Landroidx/compose/ui/graphics/BlendModeColorFilter;I)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->drawRoundRect-u-Aw5IA(JJJJLkotlin/ResultKt;FLandroidx/compose/ui/graphics/BlendModeColorFilter;I)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->getDensity()F
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->getDrawContext()Landroidx/compose/ui/graphics/drawscope/CanvasDrawScope$drawContext$1;
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->getFontScale()F
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScope;->selectPaint(Lkotlin/ResultKt;)Landroidx/compose/ui/graphics/AndroidPaint;
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScopeKt$asDrawTransform$1;-><init>(Landroidx/compose/ui/graphics/drawscope/CanvasDrawScope$drawContext$1;)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScopeKt$asDrawTransform$1;->inset(FFFF)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScopeKt$asDrawTransform$1;->scale-0AR0LA0(FFJ)V
+HSPLandroidx/compose/ui/graphics/drawscope/CanvasDrawScopeKt$asDrawTransform$1;->translate(FF)V
+HSPLandroidx/compose/ui/graphics/drawscope/DrawScope;->drawImage-AZ2fEMs$default(Landroidx/compose/ui/graphics/drawscope/DrawScope;Landroidx/compose/ui/graphics/ImageBitmap;JJJFLandroidx/compose/ui/graphics/BlendModeColorFilter;II)V
+HSPLandroidx/compose/ui/graphics/drawscope/DrawScope;->drawRect-AsUm42w$default(Landroidx/compose/ui/graphics/drawscope/DrawScope;Landroidx/compose/ui/graphics/Brush;JJFLkotlin/ResultKt;I)V
+HSPLandroidx/compose/ui/graphics/drawscope/DrawScope;->drawRect-n-J9OG0$default(Landroidx/compose/ui/graphics/drawscope/DrawScope;JJI)V
+HSPLandroidx/compose/ui/graphics/drawscope/DrawScope;->getCenter-F1C5BW0()J
+HSPLandroidx/compose/ui/graphics/drawscope/DrawScope;->getSize-NH-jbRc()J
+HSPLandroidx/compose/ui/graphics/drawscope/DrawScope;->offsetSize-PENXr5M(JJ)J
+HSPLandroidx/compose/ui/graphics/drawscope/Fill;-><clinit>()V
+HSPLandroidx/compose/ui/graphics/drawscope/Stroke;-><init>(FFIII)V
+HSPLandroidx/compose/ui/graphics/drawscope/Stroke;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/painter/BitmapPainter;-><init>(Landroidx/compose/ui/graphics/ImageBitmap;)V
+HSPLandroidx/compose/ui/graphics/painter/BitmapPainter;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/graphics/painter/BitmapPainter;->getIntrinsicSize-NH-jbRc()J
+HSPLandroidx/compose/ui/graphics/painter/BitmapPainter;->onDraw(Landroidx/compose/ui/graphics/drawscope/DrawScope;)V
+HSPLandroidx/compose/ui/graphics/painter/Painter;-><init>()V
+HSPLandroidx/compose/ui/input/InputMode;-><init>(I)V
+HSPLandroidx/compose/ui/input/InputMode;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/input/InputModeManagerImpl;-><init>(I)V
+HSPLandroidx/compose/ui/input/key/Key;-><clinit>()V
+HSPLandroidx/compose/ui/input/key/Key;->equals-impl0(JJ)Z
+HSPLandroidx/compose/ui/input/key/KeyEvent;-><init>(Landroid/view/KeyEvent;)V
+HSPLandroidx/compose/ui/input/key/KeyInputElement;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/input/key/KeyInputElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/input/key/KeyInputElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/input/key/KeyInputElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/ui/input/key/KeyInputNode;-><init>(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/input/key/Key_androidKt;->Key(I)J
+HSPLandroidx/compose/ui/input/key/Key_androidKt;->onKeyEvent(Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/input/nestedscroll/NestedScrollDispatcher;-><init>()V
+HSPLandroidx/compose/ui/input/nestedscroll/NestedScrollNode;-><init>(Landroidx/compose/foundation/gestures/ScrollableNestedScrollConnection;Landroidx/compose/ui/input/nestedscroll/NestedScrollDispatcher;)V
+HSPLandroidx/compose/ui/input/nestedscroll/NestedScrollNode;->getProvidedValues()Landroidx/tv/material3/TabKt;
+HSPLandroidx/compose/ui/input/nestedscroll/NestedScrollNode;->onAttach()V
+HSPLandroidx/compose/ui/input/nestedscroll/NestedScrollNodeKt;-><clinit>()V
+HSPLandroidx/compose/ui/input/pointer/AndroidPointerIconType;-><init>(I)V
+HSPLandroidx/compose/ui/input/pointer/MotionEventAdapter;-><init>()V
+HSPLandroidx/compose/ui/input/pointer/NodeParent;-><init>()V
+HSPLandroidx/compose/ui/input/pointer/PointerEvent;-><init>(Ljava/util/List;Lcom/google/gson/internal/ConstructorConstructor;)V
+HSPLandroidx/compose/ui/input/pointer/PointerIcon;-><clinit>()V
+HSPLandroidx/compose/ui/input/pointer/PointerKeyboardModifiers;-><init>(I)V
+HSPLandroidx/compose/ui/input/pointer/PointerKeyboardModifiers;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/input/pointer/SuspendPointerInputElement;-><init>(Ljava/lang/Object;Lokhttp3/MediaType;Lkotlin/jvm/functions/Function2;I)V
+HSPLandroidx/compose/ui/input/pointer/SuspendPointerInputElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/input/pointer/SuspendingPointerInputFilterKt;-><clinit>()V
+HSPLandroidx/compose/ui/input/pointer/SuspendingPointerInputFilterKt;->pointerInput(Landroidx/compose/ui/Modifier;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/input/pointer/SuspendingPointerInputModifierNodeImpl;-><init>(Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/ui/input/pointer/util/PointerIdArray;-><init>(FF)V
+HSPLandroidx/compose/ui/input/pointer/util/PointerIdArray;-><init>(FFLandroidx/compose/animation/core/AnimationVector;)V
+HSPLandroidx/compose/ui/input/pointer/util/PointerIdArray;-><init>(I[Landroidx/core/provider/FontsContractCompat$FontInfo;)V
+HSPLandroidx/compose/ui/input/pointer/util/PointerIdArray;-><init>(Landroidx/compose/animation/core/FloatAnimationSpec;)V
+HSPLandroidx/compose/ui/input/pointer/util/PointerIdArray;-><init>(Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;I)V
+HSPLandroidx/compose/ui/input/pointer/util/PointerIdArray;->get(I)Landroidx/compose/animation/core/FloatAnimationSpec;
+HSPLandroidx/compose/ui/input/pointer/util/VelocityTracker1D;-><init>()V
+HSPLandroidx/compose/ui/input/pointer/util/VelocityTracker;-><init>()V
+HSPLandroidx/compose/ui/input/rotary/RotaryInputElement;-><init>()V
+HSPLandroidx/compose/ui/input/rotary/RotaryInputElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/input/rotary/RotaryInputModifierKt;->onRotaryScrollEvent()Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/input/rotary/RotaryInputNode;-><init>(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/layout/AlignmentLine;-><init>(Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/ui/layout/AlignmentLineKt$FirstBaseline$1;-><clinit>()V
+HSPLandroidx/compose/ui/layout/AlignmentLineKt$FirstBaseline$1;-><init>()V
+HSPLandroidx/compose/ui/layout/AlignmentLineKt$LastBaseline$1;-><clinit>()V
+HSPLandroidx/compose/ui/layout/AlignmentLineKt$LastBaseline$1;-><init>()V
+HSPLandroidx/compose/ui/layout/AlignmentLineKt;-><clinit>()V
+HSPLandroidx/compose/ui/layout/BeyondBoundsLayoutKt;-><clinit>()V
+HSPLandroidx/compose/ui/layout/ComposableSingletons$SubcomposeLayoutKt;-><clinit>()V
+HSPLandroidx/compose/ui/layout/DefaultIntrinsicMeasurable;-><init>(Landroidx/compose/ui/layout/Measurable;Ljava/lang/Enum;Ljava/lang/Enum;I)V
+HSPLandroidx/compose/ui/layout/DefaultIntrinsicMeasurable;->getParentData()Ljava/lang/Object;
+HSPLandroidx/compose/ui/layout/FixedSizeIntrinsicsPlaceable;-><init>(III)V
+HSPLandroidx/compose/ui/layout/IntrinsicMinMax;-><clinit>()V
+HSPLandroidx/compose/ui/layout/IntrinsicMinMax;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/ui/layout/IntrinsicWidthHeight;-><clinit>()V
+HSPLandroidx/compose/ui/layout/IntrinsicWidthHeight;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/ui/layout/IntrinsicsMeasureScope;-><init>(Landroidx/compose/ui/layout/IntrinsicMeasureScope;Landroidx/compose/ui/unit/LayoutDirection;)V
+HSPLandroidx/compose/ui/layout/IntrinsicsMeasureScope;->roundToPx-0680j_4(F)I
+HSPLandroidx/compose/ui/layout/LayoutElement;-><init>(Lkotlin/jvm/functions/Function3;)V
+HSPLandroidx/compose/ui/layout/LayoutElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/layout/LayoutElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/layout/LayoutKt$materializerOf$1;-><init>(Landroidx/compose/ui/Modifier;I)V
+HSPLandroidx/compose/ui/layout/LayoutKt$materializerOf$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/layout/LayoutKt$materializerOf$1;->invoke-Deg8D_g(Landroidx/compose/runtime/Composer;Landroidx/compose/runtime/Composer;)V
+HSPLandroidx/compose/ui/layout/LayoutKt;->ScaleFactor(FF)J
+HSPLandroidx/compose/ui/layout/LayoutKt;->SubcomposeLayout(Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V
+HSPLandroidx/compose/ui/layout/LayoutKt;->SubcomposeLayout(Landroidx/compose/ui/layout/SubcomposeLayoutState;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V
+HSPLandroidx/compose/ui/layout/LayoutKt;->findRootCoordinates(Landroidx/compose/ui/node/NodeCoordinator;)Landroidx/compose/ui/layout/LayoutCoordinates;
+HSPLandroidx/compose/ui/layout/LayoutKt;->layout(Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function3;)Landroidx/compose/ui/Modifier;
+HSPLandroidx/compose/ui/layout/LayoutKt;->modifierMaterializerOf(Landroidx/compose/ui/Modifier;)Landroidx/compose/runtime/internal/ComposableLambdaImpl;
+HSPLandroidx/compose/ui/layout/LayoutKt;->times-UQTWf7w(JJ)J
+HSPLandroidx/compose/ui/layout/LayoutModifierImpl;-><init>(Lkotlin/jvm/functions/Function3;)V
+HSPLandroidx/compose/ui/layout/LayoutModifierImpl;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$NodeState;-><init>(Ljava/lang/Object;Landroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$PostLookaheadMeasureScopeImpl;-><init>(Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState;)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$Scope;-><init>(Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState;)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$Scope;->getDensity()F
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$Scope;->getLayoutDirection()Landroidx/compose/ui/unit/LayoutDirection;
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$Scope;->isLookingAhead()Z
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$Scope;->subcompose(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/util/List;
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1$measure-3p2s80s$$inlined$createMeasureResult$1;-><init>(Landroidx/compose/ui/layout/MeasureResult;Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState;ILandroidx/compose/ui/layout/MeasureResult;I)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1$measure-3p2s80s$$inlined$createMeasureResult$1;->getAlignmentLines()Ljava/util/Map;
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1$measure-3p2s80s$$inlined$createMeasureResult$1;->getHeight()I
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1$measure-3p2s80s$$inlined$createMeasureResult$1;->getWidth()I
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1$measure-3p2s80s$$inlined$createMeasureResult$1;->placeChildren()V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1;-><init>(Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState;Lkotlin/jvm/functions/Function2;Ljava/lang/String;)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Ljava/util/List;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$precompose$1;-><init>(Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState;Ljava/lang/Object;)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$precompose$1;->dispose()V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState$precompose$1;->premeasure-0kLqBqw(JI)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState;-><init>(Landroidx/compose/ui/node/LayoutNode;Landroidx/compose/ui/layout/SubcomposeSlotReusePolicy;)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState;->disposeOrReuseStartingFromIndex(I)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState;->makeSureStateIsConsistent()V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState;->precompose(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState$precompose$1;
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState;->subcompose(Landroidx/compose/ui/node/LayoutNode;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/ui/layout/LayoutNodeSubcompositionsState;->takeNodeFromReusables(Ljava/lang/Object;)Landroidx/compose/ui/node/LayoutNode;
+HSPLandroidx/compose/ui/layout/MeasurePolicy;->maxIntrinsicHeight(Landroidx/compose/ui/node/NodeCoordinator;Ljava/util/List;I)I
+HSPLandroidx/compose/ui/layout/MeasureScope$layout$1;-><init>(IILjava/util/Map;Landroidx/compose/ui/layout/MeasureScope;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/layout/MeasureScope$layout$1;->getAlignmentLines()Ljava/util/Map;
+HSPLandroidx/compose/ui/layout/MeasureScope$layout$1;->getHeight()I
+HSPLandroidx/compose/ui/layout/MeasureScope$layout$1;->getWidth()I
+HSPLandroidx/compose/ui/layout/MeasureScope$layout$1;->placeChildren()V
+HSPLandroidx/compose/ui/layout/MeasureScope;->layout$default(Landroidx/compose/ui/layout/MeasureScope;IILkotlin/jvm/functions/Function1;)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/layout/MeasureScope;->layout(IILjava/util/Map;Lkotlin/jvm/functions/Function1;)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/layout/OnSizeChangedModifier;-><init>(Landroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect$onNewSize$1;)V
+HSPLandroidx/compose/ui/layout/PinnableContainerKt;-><clinit>()V
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope$Companion;->access$configureForPlacingForAlignment(Landroidx/compose/ui/node/LookaheadCapablePlaceable;)Z
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope;-><clinit>()V
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope;->place(Landroidx/compose/ui/layout/Placeable;IIF)V
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope;->place-70tqf50$default(Landroidx/compose/ui/layout/Placeable$PlacementScope;Landroidx/compose/ui/layout/Placeable;J)V
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope;->place-70tqf50(Landroidx/compose/ui/layout/Placeable;JF)V
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope;->placeRelative$default(Landroidx/compose/ui/layout/Placeable$PlacementScope;Landroidx/compose/ui/layout/Placeable;II)V
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope;->placeRelativeWithLayer$default(Landroidx/compose/ui/layout/Placeable$PlacementScope;Landroidx/compose/ui/layout/Placeable;II)V
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope;->placeRelativeWithLayer-aW-9-wM$default(Landroidx/compose/ui/layout/Placeable$PlacementScope;Landroidx/compose/ui/layout/Placeable;J)V
+HSPLandroidx/compose/ui/layout/Placeable$PlacementScope;->placeWithLayer$default(Landroidx/compose/ui/layout/Placeable$PlacementScope;Landroidx/compose/ui/layout/Placeable;IILkotlin/jvm/functions/Function1;I)V
+HSPLandroidx/compose/ui/layout/Placeable;-><init>()V
+HSPLandroidx/compose/ui/layout/Placeable;->getMeasuredHeight()I
+HSPLandroidx/compose/ui/layout/Placeable;->getMeasuredWidth()I
+HSPLandroidx/compose/ui/layout/Placeable;->onMeasuredSizeChanged()V
+HSPLandroidx/compose/ui/layout/Placeable;->setMeasuredSize-ozmzZPI(J)V
+HSPLandroidx/compose/ui/layout/Placeable;->setMeasurementConstraints-BRTryo0(J)V
+HSPLandroidx/compose/ui/layout/PlaceableKt;-><clinit>()V
+HSPLandroidx/compose/ui/layout/RootMeasurePolicy$measure$2;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/layout/RootMeasurePolicy$measure$2;->invoke(Landroidx/compose/ui/layout/Placeable$PlacementScope;)V
+HSPLandroidx/compose/ui/layout/RootMeasurePolicy$measure$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/layout/RootMeasurePolicy;-><clinit>()V
+HSPLandroidx/compose/ui/layout/RootMeasurePolicy;-><init>()V
+HSPLandroidx/compose/ui/layout/RootMeasurePolicy;->measure-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Ljava/util/List;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/layout/ScaleFactor;-><clinit>()V
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$2;-><init>(Ljava/lang/Object;ILjava/lang/Object;II)V
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$2;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$4;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$4;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$5$1$invoke$$inlined$onDispose$1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$5$1$invoke$$inlined$onDispose$1;->dispose()V
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutState$setRoot$1;-><init>(Landroidx/compose/ui/layout/SubcomposeLayoutState;I)V
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutState$setRoot$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutState;-><init>(Landroidx/compose/ui/layout/SubcomposeSlotReusePolicy;)V
+HSPLandroidx/compose/ui/layout/SubcomposeLayoutState;->getState()Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState;
+HSPLandroidx/compose/ui/layout/SubcomposeSlotReusePolicy$SlotIdsSet;-><init>()V
+HSPLandroidx/compose/ui/layout/SubcomposeSlotReusePolicy$SlotIdsSet;->clear()V
+HSPLandroidx/compose/ui/layout/SubcomposeSlotReusePolicy$SlotIdsSet;->contains(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/layout/SubcomposeSlotReusePolicy$SlotIdsSet;->iterator()Ljava/util/Iterator;
+HSPLandroidx/compose/ui/modifier/BackwardsCompatLocalMap;-><init>(Landroidx/compose/ui/modifier/ModifierLocalProvider;)V
+HSPLandroidx/compose/ui/modifier/BackwardsCompatLocalMap;->contains$ui_release(Landroidx/compose/ui/modifier/ModifierLocal;)Z
+HSPLandroidx/compose/ui/modifier/EmptyMap;-><clinit>()V
+HSPLandroidx/compose/ui/modifier/EmptyMap;->contains$ui_release(Landroidx/compose/ui/modifier/ModifierLocal;)Z
+HSPLandroidx/compose/ui/modifier/ModifierLocal;-><init>(Landroidx/compose/material3/ShapesKt$LocalShapes$1;)V
+HSPLandroidx/compose/ui/modifier/ModifierLocalManager;-><init>(Landroidx/compose/ui/node/Owner;)V
+HSPLandroidx/compose/ui/modifier/ModifierLocalModifierNode;->getCurrent(Landroidx/compose/ui/modifier/ProvidableModifierLocal;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/modifier/ModifierLocalModifierNode;->getProvidedValues()Landroidx/tv/material3/TabKt;
+HSPLandroidx/compose/ui/modifier/SingleLocalMap;-><init>(Landroidx/compose/ui/modifier/ModifierLocal;)V
+HSPLandroidx/compose/ui/modifier/SingleLocalMap;->contains$ui_release(Landroidx/compose/ui/modifier/ModifierLocal;)Z
+HSPLandroidx/compose/ui/modifier/SingleLocalMap;->get$ui_release(Landroidx/compose/ui/modifier/ProvidableModifierLocal;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/AlignmentLines;-><init>(Landroidx/compose/ui/node/AlignmentLinesOwner;)V
+HSPLandroidx/compose/ui/node/AlignmentLines;->getQueried$ui_release()Z
+HSPLandroidx/compose/ui/node/AlignmentLines;->getRequired$ui_release()Z
+HSPLandroidx/compose/ui/node/AlignmentLines;->onAlignmentsChanged()V
+HSPLandroidx/compose/ui/node/AlignmentLines;->recalculateQueryOwner()V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;-><init>(Landroidx/compose/ui/Modifier$Element;)V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->draw(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->getProvidedValues()Landroidx/tv/material3/TabKt;
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->initializeModifier(Z)V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->onAttach()V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->onGloballyPositioned(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->onMeasureResultChanged()V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->onPlaced(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->onRemeasured-ozmzZPI(J)V
+HSPLandroidx/compose/ui/node/BackwardsCompatNode;->unInitializeModifier()V
+HSPLandroidx/compose/ui/node/CanFocusChecker;-><clinit>()V
+HSPLandroidx/compose/ui/node/CanFocusChecker;->setCanFocus(Z)V
+HSPLandroidx/compose/ui/node/ComposeUiNode$Companion;-><clinit>()V
+HSPLandroidx/compose/ui/node/ComposeUiNode;-><clinit>()V
+HSPLandroidx/compose/ui/node/DelegatingNode;-><init>()V
+HSPLandroidx/compose/ui/node/DelegatingNode;->delegate(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/ui/node/DelegatingNode;->markAsAttached$ui_release()V
+HSPLandroidx/compose/ui/node/DelegatingNode;->markAsDetached$ui_release()V
+HSPLandroidx/compose/ui/node/DelegatingNode;->reset$ui_release()V
+HSPLandroidx/compose/ui/node/DelegatingNode;->runAttachLifecycle$ui_release()V
+HSPLandroidx/compose/ui/node/DelegatingNode;->runDetachLifecycle$ui_release()V
+HSPLandroidx/compose/ui/node/DelegatingNode;->updateCoordinator$ui_release(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/ui/node/DrawModifierNode;->onMeasureResultChanged()V
+HSPLandroidx/compose/ui/node/HitTestResult;-><init>()V
+HSPLandroidx/compose/ui/node/InnerNodeCoordinator;-><clinit>()V
+HSPLandroidx/compose/ui/node/InnerNodeCoordinator;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/InnerNodeCoordinator;->getTail()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/node/InnerNodeCoordinator;->maxIntrinsicHeight(I)I
+HSPLandroidx/compose/ui/node/InnerNodeCoordinator;->measure-BRTryo0(J)Landroidx/compose/ui/layout/Placeable;
+HSPLandroidx/compose/ui/node/InnerNodeCoordinator;->performDraw(Landroidx/compose/ui/graphics/Canvas;)V
+HSPLandroidx/compose/ui/node/InnerNodeCoordinator;->placeAt-f8xVGno(JFLkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/node/IntrinsicsPolicy;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/IntrinsicsPolicy;->measurePolicyFromState()Landroidx/compose/ui/layout/MeasurePolicy;
+HSPLandroidx/compose/ui/node/LayerPositionalProperties;-><init>()V
+HSPLandroidx/compose/ui/node/LayoutAwareModifierNode;->onRemeasured-ozmzZPI(J)V
+HSPLandroidx/compose/ui/node/LayoutModifierNode;->maxIntrinsicHeight(Landroidx/compose/ui/layout/IntrinsicMeasureScope;Landroidx/compose/ui/layout/Measurable;I)I
+HSPLandroidx/compose/ui/node/LayoutModifierNode;->maxIntrinsicWidth(Landroidx/compose/ui/layout/IntrinsicMeasureScope;Landroidx/compose/ui/layout/Measurable;I)I
+HSPLandroidx/compose/ui/node/LayoutModifierNodeCoordinator;-><clinit>()V
+HSPLandroidx/compose/ui/node/LayoutModifierNodeCoordinator;-><init>(Landroidx/compose/ui/node/LayoutNode;Landroidx/compose/ui/node/LayoutModifierNode;)V
+HSPLandroidx/compose/ui/node/LayoutModifierNodeCoordinator;->getTail()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/node/LayoutModifierNodeCoordinator;->maxIntrinsicHeight(I)I
+HSPLandroidx/compose/ui/node/LayoutModifierNodeCoordinator;->maxIntrinsicWidth(I)I
+HSPLandroidx/compose/ui/node/LayoutModifierNodeCoordinator;->measure-BRTryo0(J)Landroidx/compose/ui/layout/Placeable;
+HSPLandroidx/compose/ui/node/LayoutModifierNodeCoordinator;->performDraw(Landroidx/compose/ui/graphics/Canvas;)V
+HSPLandroidx/compose/ui/node/LayoutModifierNodeCoordinator;->placeAt-f8xVGno(JFLkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/node/LayoutNode$$ExternalSyntheticLambda0;-><init>(I)V
+HSPLandroidx/compose/ui/node/LayoutNode$$ExternalSyntheticLambda0;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
+HSPLandroidx/compose/ui/node/LayoutNode$Companion$ErrorMeasurePolicy$1;-><init>()V
+HSPLandroidx/compose/ui/node/LayoutNode$NoIntrinsicsMeasurePolicy;-><init>(Ljava/lang/String;)V
+HSPLandroidx/compose/ui/node/LayoutNode$WhenMappings;-><clinit>()V
+HSPLandroidx/compose/ui/node/LayoutNode$_foldedChildren$1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/node/LayoutNode$_foldedChildren$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/LayoutNode$_foldedChildren$1;->invoke()V
+HSPLandroidx/compose/ui/node/LayoutNode;-><clinit>()V
+HSPLandroidx/compose/ui/node/LayoutNode;-><init>(IZ)V
+HSPLandroidx/compose/ui/node/LayoutNode;-><init>(ZI)V
+HSPLandroidx/compose/ui/node/LayoutNode;->attach$ui_release(Landroidx/compose/ui/node/Owner;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->clearSubtreeIntrinsicsUsage$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNode;->clearSubtreePlacementIntrinsicsUsage()V
+HSPLandroidx/compose/ui/node/LayoutNode;->draw$ui_release(Landroidx/compose/ui/graphics/Canvas;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->forceRemeasure()V
+HSPLandroidx/compose/ui/node/LayoutNode;->getChildMeasurables$ui_release()Ljava/util/List;
+HSPLandroidx/compose/ui/node/LayoutNode;->getChildren$ui_release()Ljava/util/List;
+HSPLandroidx/compose/ui/node/LayoutNode;->getFoldedChildren$ui_release()Ljava/util/List;
+HSPLandroidx/compose/ui/node/LayoutNode;->getMeasuredByParent$ui_release$enumunboxing$()I
+HSPLandroidx/compose/ui/node/LayoutNode;->getParent$ui_release()Landroidx/compose/ui/node/LayoutNode;
+HSPLandroidx/compose/ui/node/LayoutNode;->getPlaceOrder$ui_release()I
+HSPLandroidx/compose/ui/node/LayoutNode;->getZSortedChildren()Landroidx/compose/runtime/collection/MutableVector;
+HSPLandroidx/compose/ui/node/LayoutNode;->get_children$ui_release()Landroidx/compose/runtime/collection/MutableVector;
+HSPLandroidx/compose/ui/node/LayoutNode;->insertAt$ui_release(ILandroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->invalidateLayer$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNode;->invalidateLayers$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNode;->invalidateMeasurements$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNode;->invalidateSemantics$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNode;->invalidateUnfoldedVirtualChildren()V
+HSPLandroidx/compose/ui/node/LayoutNode;->isAttached()Z
+HSPLandroidx/compose/ui/node/LayoutNode;->isPlaced()Z
+HSPLandroidx/compose/ui/node/LayoutNode;->isValidOwnerScope()Z
+HSPLandroidx/compose/ui/node/LayoutNode;->move$ui_release(III)V
+HSPLandroidx/compose/ui/node/LayoutNode;->onZSortedChildrenInvalidated$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNode;->requestRelayout$ui_release(Z)V
+HSPLandroidx/compose/ui/node/LayoutNode;->requestRemeasure$ui_release$default(Landroidx/compose/ui/node/LayoutNode;ZI)V
+HSPLandroidx/compose/ui/node/LayoutNode;->rescheduleRemeasureOrRelayout$ui_release(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->resetSubtreeIntrinsicsUsage$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNode;->setCompositionLocalMap(Landroidx/compose/runtime/CompositionLocalMap;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->setDensity(Landroidx/compose/ui/unit/Density;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->setLookaheadRoot(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->setMeasurePolicy(Landroidx/compose/ui/layout/MeasurePolicy;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->setModifier(Landroidx/compose/ui/Modifier;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->setViewConfiguration(Landroidx/compose/ui/platform/ViewConfiguration;)V
+HSPLandroidx/compose/ui/node/LayoutNode;->updateChildrenIfDirty$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;-><init>()V
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->drawContent()V
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->drawDirect-x_KDEd0$ui_release(Landroidx/compose/ui/graphics/Canvas;JLandroidx/compose/ui/node/NodeCoordinator;Landroidx/compose/ui/node/DrawModifierNode;)V
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->drawImage-AZ2fEMs(Landroidx/compose/ui/graphics/ImageBitmap;JJJJFLkotlin/ResultKt;Landroidx/compose/ui/graphics/BlendModeColorFilter;II)V
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->drawRect-AsUm42w(Landroidx/compose/ui/graphics/Brush;JJFLkotlin/ResultKt;Landroidx/compose/ui/graphics/BlendModeColorFilter;I)V
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->drawRect-n-J9OG0(JJJFLkotlin/ResultKt;Landroidx/compose/ui/graphics/BlendModeColorFilter;I)V
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->drawRoundRect-u-Aw5IA(JJJJLkotlin/ResultKt;FLandroidx/compose/ui/graphics/BlendModeColorFilter;I)V
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->getCenter-F1C5BW0()J
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->getDensity()F
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->getFontScale()F
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->getLayoutDirection()Landroidx/compose/ui/unit/LayoutDirection;
+HSPLandroidx/compose/ui/node/LayoutNodeDrawScope;->getSize-NH-jbRc()J
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate$placeOuterCoordinator$1;-><init>(Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/node/LayoutNodeLayoutDelegate;JF)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate$placeOuterCoordinator$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;-><init>(Landroidx/compose/ui/node/LayoutNodeLayoutDelegate;)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->forEachChildAlignmentLinesOwner(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->getAlignmentLines()Landroidx/compose/ui/node/LookaheadAlignmentLines;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->getChildDelegates$ui_release()Ljava/util/List;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->getInnerCoordinator()Landroidx/compose/ui/node/InnerNodeCoordinator;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->getMeasuredWidth()I
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->getParentAlignmentLinesOwner()Landroidx/compose/ui/node/AlignmentLinesOwner;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->getParentData()Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->layoutChildren()V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->markNodeAndSubtreeAsPlaced()V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->markSubtreeAsNotPlaced()V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->maxIntrinsicHeight(I)I
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->maxIntrinsicWidth(I)I
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->measure-BRTryo0(J)Landroidx/compose/ui/layout/Placeable;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->notifyChildrenUsingCoordinatesWhilePlacing()V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->onIntrinsicsQueried()V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->onNodePlaced$ui_release()V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->placeAt-f8xVGno(JFLkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->placeOuterCoordinator-f8xVGno(JFLkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->remeasure-BRTryo0(J)Z
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;->replace()V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$performMeasure$2;-><init>(Landroidx/compose/ui/node/LayoutNodeLayoutDelegate;JI)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$performMeasure$2;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate$performMeasure$2;->invoke()V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate;->getOuterCoordinator()Landroidx/compose/ui/node/NodeCoordinator;
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate;->isOutMostLookaheadRoot(Landroidx/compose/ui/node/LayoutNode;)Z
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate;->setCoordinatesAccessedDuringModifierPlacement(Z)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate;->setCoordinatesAccessedDuringPlacement(Z)V
+HSPLandroidx/compose/ui/node/LayoutNodeLayoutDelegate;->updateParentData()V
+HSPLandroidx/compose/ui/node/LookaheadAlignmentLines;-><init>(Landroidx/compose/ui/node/AlignmentLinesOwner;I)V
+HSPLandroidx/compose/ui/node/LookaheadCapablePlaceable;->invalidateAlignmentLinesFromPositionChange(Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/ui/node/LookaheadCapablePlaceable;->isLookingAhead()Z
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->dispatchOnPositionedCallbacks(Z)V
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->doLookaheadRemeasure-sdFAvZA(Landroidx/compose/ui/node/LayoutNode;Landroidx/compose/ui/unit/Constraints;)Z
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->doRemeasure-sdFAvZA(Landroidx/compose/ui/node/LayoutNode;Landroidx/compose/ui/unit/Constraints;)Z
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->forceMeasureTheSubtree(Landroidx/compose/ui/node/LayoutNode;Z)V
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->measureAndLayout(Lkotlin/jvm/functions/Function0;)Z
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->measureAndLayout-0kLqBqw(Landroidx/compose/ui/node/LayoutNode;J)V
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->measureOnly()V
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->recurseRemeasure(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->remeasureAndRelayoutIfNeeded(Landroidx/compose/ui/node/LayoutNode;Z)Z
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->remeasureOnly(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->requestRelayout(Landroidx/compose/ui/node/LayoutNode;Z)Z
+HSPLandroidx/compose/ui/node/MeasureAndLayoutDelegate;->updateRootConstraints-BRTryo0(J)V
+HSPLandroidx/compose/ui/node/NodeChain$Differ;-><init>(Landroidx/compose/ui/node/NodeChain;Landroidx/compose/ui/Modifier$Node;ILandroidx/compose/runtime/collection/MutableVector;Landroidx/compose/runtime/collection/MutableVector;Z)V
+HSPLandroidx/compose/ui/node/NodeChain$Differ;->areItemsTheSame(II)Z
+HSPLandroidx/compose/ui/node/NodeChain;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/NodeChain;->access$propagateCoordinator(Landroidx/compose/ui/node/NodeChain;Landroidx/compose/ui/Modifier$Node;Landroidx/compose/ui/node/NodeCoordinator;)V
+HSPLandroidx/compose/ui/node/NodeChain;->createAndInsertNodeAsChild(Landroidx/compose/ui/Modifier$Element;Landroidx/compose/ui/Modifier$Node;)Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/node/NodeChain;->detachAndRemoveNode(Landroidx/compose/ui/Modifier$Node;)Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/node/NodeChain;->has-H91voCI$ui_release(I)Z
+HSPLandroidx/compose/ui/node/NodeChain;->runAttachLifecycle()V
+HSPLandroidx/compose/ui/node/NodeChain;->syncCoordinators()V
+HSPLandroidx/compose/ui/node/NodeChain;->updateNode(Landroidx/compose/ui/Modifier$Element;Landroidx/compose/ui/Modifier$Element;Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/ui/node/NodeChainKt;-><clinit>()V
+HSPLandroidx/compose/ui/node/NodeChainKt;->actionForModifiers(Landroidx/compose/ui/Modifier$Element;Landroidx/compose/ui/Modifier$Element;)I
+HSPLandroidx/compose/ui/node/NodeCoordinator$invoke$1;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/node/NodeCoordinator$invoke$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/NodeCoordinator$invoke$1;->invoke()V
+HSPLandroidx/compose/ui/node/NodeCoordinator;-><clinit>()V
+HSPLandroidx/compose/ui/node/NodeCoordinator;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->ancestorToLocal(Landroidx/compose/ui/node/NodeCoordinator;Landroidx/compose/ui/geometry/MutableRect;Z)V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->draw(Landroidx/compose/ui/graphics/Canvas;)V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->drawContainedDrawModifiers(Landroidx/compose/ui/graphics/Canvas;)V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->findCommonAncestor$ui_release(Landroidx/compose/ui/node/NodeCoordinator;)Landroidx/compose/ui/node/NodeCoordinator;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getAlignmentLinesOwner()Landroidx/compose/ui/node/AlignmentLinesOwner;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getCoordinates()Landroidx/compose/ui/layout/LayoutCoordinates;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getDensity()F
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getFontScale()F
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getLayoutDirection()Landroidx/compose/ui/unit/LayoutDirection;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getLayoutNode()Landroidx/compose/ui/node/LayoutNode;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getMeasureResult$ui_release()Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getParent()Landroidx/compose/ui/node/LookaheadCapablePlaceable;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getParentData()Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getParentLayoutCoordinates()Landroidx/compose/ui/layout/LayoutCoordinates;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->getSize-YbymL2g()J
+HSPLandroidx/compose/ui/node/NodeCoordinator;->head-H91voCI(I)Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->headNode(Z)Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->invalidateLayer()V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->isAttached()Z
+HSPLandroidx/compose/ui/node/NodeCoordinator;->isValidOwnerScope()Z
+HSPLandroidx/compose/ui/node/NodeCoordinator;->localBoundingBoxOf(Landroidx/compose/ui/layout/LayoutCoordinates;Z)Landroidx/compose/ui/geometry/Rect;
+HSPLandroidx/compose/ui/node/NodeCoordinator;->localToRoot-MK-Hz9U(J)J
+HSPLandroidx/compose/ui/node/NodeCoordinator;->onCoordinatesUsed$ui_release()V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->onMeasured()V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->onPlaced()V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->placeSelf-f8xVGno(JFLkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->rectInParent$ui_release(Landroidx/compose/ui/geometry/MutableRect;ZZ)V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->setMeasureResult$ui_release(Landroidx/compose/ui/layout/MeasureResult;)V
+HSPLandroidx/compose/ui/node/NodeCoordinator;->toParentPosition-MK-Hz9U(J)J
+HSPLandroidx/compose/ui/node/NodeCoordinator;->updateLayerParameters(Z)V
+HSPLandroidx/compose/ui/node/NodeMeasuringIntrinsics$IntrinsicMinMax;-><clinit>()V
+HSPLandroidx/compose/ui/node/NodeMeasuringIntrinsics$IntrinsicMinMax;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/ui/node/NodeMeasuringIntrinsics$IntrinsicWidthHeight;-><clinit>()V
+HSPLandroidx/compose/ui/node/NodeMeasuringIntrinsics$IntrinsicWidthHeight;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/ui/node/ObserverNodeOwnerScope;-><init>(Landroidx/compose/ui/node/ObserverModifierNode;)V
+HSPLandroidx/compose/ui/node/ObserverNodeOwnerScope;->isValidOwnerScope()Z
+HSPLandroidx/compose/ui/node/OnPositionedDispatcher$Companion$DepthComparator;-><clinit>()V
+HSPLandroidx/compose/ui/node/OnPositionedDispatcher$Companion$DepthComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
+HSPLandroidx/compose/ui/node/OnPositionedDispatcher;-><init>()V
+HSPLandroidx/compose/ui/node/OnPositionedDispatcher;->dispatchHierarchy(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/Owner;->measureAndLayout$default(Landroidx/compose/ui/node/Owner;)V
+HSPLandroidx/compose/ui/node/OwnerSnapshotObserver;-><init>(Landroidx/compose/ui/platform/AndroidComposeView$focusOwner$1;)V
+HSPLandroidx/compose/ui/node/OwnerSnapshotObserver;->observeLayoutModifierSnapshotReads$ui_release(Landroidx/compose/ui/node/LayoutNode;ZLkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/ui/node/OwnerSnapshotObserver;->observeReads$ui_release(Landroidx/compose/ui/node/OwnerScope;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/ui/node/TailModifierNode;-><init>()V
+HSPLandroidx/compose/ui/node/TailModifierNode;->onAttach()V
+HSPLandroidx/compose/ui/node/TailModifierNode;->onDetach()V
+HSPLandroidx/compose/ui/node/UiApplier;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/node/UiApplier;->down(Ljava/lang/Object;)V
+HSPLandroidx/compose/ui/node/UiApplier;->getCurrent()Ljava/lang/Object;
+HSPLandroidx/compose/ui/node/UiApplier;->insertBottomUp(ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/node/UiApplier;->insertTopDown(ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/node/UiApplier;->onEndChanges()V
+HSPLandroidx/compose/ui/node/UiApplier;->up()V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->addView(Landroid/view/View;ILandroid/view/ViewGroup$LayoutParams;)V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->addView(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->checkAddView()V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->ensureCompositionCreated()V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->isAlive(Landroidx/compose/runtime/CompositionContext;)Z
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->onAttachedToWindow()V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->onLayout(ZIIII)V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->onMeasure(II)V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->onRtlPropertiesChanged(I)V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->resolveParentCompositionContext()Landroidx/compose/runtime/CompositionContext;
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->setParentCompositionContext(Landroidx/compose/runtime/CompositionContext;)V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->setParentContext(Landroidx/compose/runtime/CompositionContext;)V
+HSPLandroidx/compose/ui/platform/AbstractComposeView;->setPreviousAttachedWindowToken(Landroid/os/IBinder;)V
+HSPLandroidx/compose/ui/platform/AndroidAccessibilityManager;-><init>(Landroid/content/Context;)V
+HSPLandroidx/compose/ui/platform/AndroidClipboardManager;-><init>(Landroid/content/Context;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticApiModelOutline0;->m(Landroid/content/res/Configuration;)I
+HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticApiModelOutline0;->m(Landroid/view/View;Landroid/view/translation/ViewTranslationCallback;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticLambda1;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticLambda1;->onGlobalLayout()V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticLambda2;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticLambda3;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticLambda3;->onTouchModeChanged(Z)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$ViewTreeOwners;-><init>(Landroidx/lifecycle/LifecycleOwner;Landroidx/savedstate/SavedStateRegistryOwner;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$focusOwner$1;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;I)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$focusOwner$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/AndroidComposeView$focusOwner$1;->invoke(Lkotlin/jvm/functions/Function0;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$pointerIconService$1;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$viewTreeOwners$2;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;I)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView$viewTreeOwners$2;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;-><clinit>()V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;-><init>(Landroid/content/Context;Lkotlin/coroutines/CoroutineContext;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->access$get_viewTreeOwners(Landroidx/compose/ui/platform/AndroidComposeView;)Landroidx/compose/ui/platform/AndroidComposeView$ViewTreeOwners;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->convertMeasureSpec-I7RO_PI(I)J
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->dispatchDraw(Landroid/graphics/Canvas;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->dispatchKeyEvent(Landroid/view/KeyEvent;)Z
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->dispatchKeyEventPreIme(Landroid/view/KeyEvent;)Z
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->forceMeasureTheSubtree(Landroidx/compose/ui/node/LayoutNode;Z)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getAccessibilityManager()Landroidx/compose/ui/platform/AccessibilityManager;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getAccessibilityManager()Landroidx/compose/ui/platform/AndroidAccessibilityManager;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getAutofill()Landroidx/compose/ui/autofill/Autofill;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getAutofillTree()Landroidx/compose/ui/autofill/AutofillTree;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getClipboardManager()Landroidx/compose/ui/platform/AndroidClipboardManager;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getClipboardManager()Landroidx/compose/ui/platform/ClipboardManager;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getCoroutineContext()Lkotlin/coroutines/CoroutineContext;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getDensity()Landroidx/compose/ui/unit/Density;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getFocusOwner()Landroidx/compose/ui/focus/FocusOwner;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getFontFamilyResolver()Landroidx/compose/ui/text/font/FontFamily$Resolver;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getFontLoader()Landroidx/compose/ui/text/font/Font$ResourceLoader;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getHapticFeedBack()Landroidx/compose/ui/hapticfeedback/HapticFeedback;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getInputModeManager()Landroidx/compose/ui/input/InputModeManager;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getLayoutDirection()Landroidx/compose/ui/unit/LayoutDirection;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getPointerIconService()Landroidx/compose/ui/input/pointer/PointerIconService;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getRoot()Landroidx/compose/ui/node/LayoutNode;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getSemanticsOwner()Landroidx/compose/ui/semantics/SemanticsOwner;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getSharedDrawScope()Landroidx/compose/ui/node/LayoutNodeDrawScope;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getShowLayoutBounds()Z
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getSnapshotObserver()Landroidx/compose/ui/node/OwnerSnapshotObserver;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getSoftwareKeyboardController()Landroidx/compose/ui/platform/SoftwareKeyboardController;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getTextInputService()Landroidx/compose/ui/text/input/TextInputService;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getTextToolbar()Landroidx/compose/ui/platform/TextToolbar;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getView()Landroid/view/View;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getViewConfiguration()Landroidx/compose/ui/platform/ViewConfiguration;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getViewTreeOwners()Landroidx/compose/ui/platform/AndroidComposeView$ViewTreeOwners;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->getWindowInfo()Landroidx/compose/ui/platform/WindowInfo;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->get_viewTreeOwners()Landroidx/compose/ui/platform/AndroidComposeView$ViewTreeOwners;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->invalidateLayers(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->invalidateLayoutNodeMeasurement(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->measureAndLayout(Z)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->measureAndLayout-0kLqBqw(Landroidx/compose/ui/node/LayoutNode;J)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->notifyLayerIsDirty$ui_release(Landroidx/compose/ui/node/OwnedLayer;Z)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onAttachedToWindow()V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onCheckIsTextEditor()Z
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onCreateInputConnection(Landroid/view/inputmethod/EditorInfo;)Landroid/view/inputmethod/InputConnection;
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onDraw(Landroid/graphics/Canvas;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onEndApplyChanges()V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onFocusChanged(ZILandroid/graphics/Rect;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onLayout(ZIIII)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onLayoutChange(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onMeasure(II)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onRequestRelayout(Landroidx/compose/ui/node/LayoutNode;ZZ)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onResume(Landroidx/lifecycle/LifecycleOwner;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onRtlPropertiesChanged(I)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onSemanticsChange()V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->onWindowFocusChanged(Z)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->pack-ZIaKswc(II)J
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->recycle$ui_release(Landroidx/compose/ui/node/OwnedLayer;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->scheduleMeasureAndLayout(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->setConfigurationChangeObserver(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->setLayoutDirection(Landroidx/compose/ui/unit/LayoutDirection;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->setOnViewTreeOwnersAvailable(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->setShowLayoutBounds(Z)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->set_viewTreeOwners(Landroidx/compose/ui/platform/AndroidComposeView$ViewTreeOwners;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeView;->updatePositionCacheAndDispatch()V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$$ExternalSyntheticLambda1;-><init>(Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$$ExternalSyntheticLambda2;-><init>(Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$1;->onViewAttachedToWindow(Landroid/view/View;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$MyNodeProvider;-><init>(Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$SemanticsNodeCopy;-><init>(Landroidx/compose/ui/semantics/SemanticsNode;Ljava/util/Map;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$boundsUpdatesEventLoop$1;-><init>(Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;-><clinit>()V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;->boundsUpdatesEventLoop(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;->getAccessibilityNodeProvider(Landroid/view/View;)Landroidx/core/view/accessibility/AccessibilityNodeProviderCompat;
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;->isEnabledForAccessibility()Z
+HSPLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;->onStart(Landroidx/lifecycle/LifecycleOwner;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewForceDarkModeQ;-><clinit>()V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewForceDarkModeQ;->disallowForceDark(Landroid/view/View;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewTranslationCallbackS;-><clinit>()V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewTranslationCallbackS;->setViewTranslationCallback(Landroid/view/View;Landroid/view/translation/ViewTranslationCallback;)V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewVerificationHelperMethodsO;-><clinit>()V
+HSPLandroidx/compose/ui/platform/AndroidComposeViewVerificationHelperMethodsO;->focusable(Landroid/view/View;IZ)V
+HSPLandroidx/compose/ui/platform/AndroidCompositionLocals_androidKt$obtainImageVectorCache$callbacks$1$1;-><init>(Landroid/content/res/Configuration;Landroidx/compose/ui/res/ImageVectorCache;)V
+HSPLandroidx/compose/ui/platform/AndroidCompositionLocals_androidKt;-><clinit>()V
+HSPLandroidx/compose/ui/platform/AndroidCompositionLocals_androidKt;->ProvideAndroidCompositionLocals(Landroidx/compose/ui/platform/AndroidComposeView;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/ui/platform/AndroidUiDispatcher$dispatchCallback$1;-><init>(Landroidx/compose/ui/platform/AndroidUiDispatcher;)V
+HSPLandroidx/compose/ui/platform/AndroidUiDispatcher$dispatchCallback$1;->doFrame(J)V
+HSPLandroidx/compose/ui/platform/AndroidUiDispatcher$dispatchCallback$1;->run()V
+HSPLandroidx/compose/ui/platform/AndroidUiDispatcher;-><clinit>()V
+HSPLandroidx/compose/ui/platform/AndroidUiDispatcher;-><init>(Landroid/view/Choreographer;Landroid/os/Handler;)V
+HSPLandroidx/compose/ui/platform/AndroidUiDispatcher;->access$performTrampolineDispatch(Landroidx/compose/ui/platform/AndroidUiDispatcher;)V
+HSPLandroidx/compose/ui/platform/AndroidUiDispatcher;->dispatch(Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
+HSPLandroidx/compose/ui/platform/AndroidUiFrameClock$withFrameNanos$2$callback$1;-><init>(Lkotlinx/coroutines/CancellableContinuationImpl;Landroidx/compose/ui/platform/AndroidUiFrameClock;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/platform/AndroidUiFrameClock$withFrameNanos$2$callback$1;->doFrame(J)V
+HSPLandroidx/compose/ui/platform/AndroidUiFrameClock;-><init>(Landroid/view/Choreographer;Landroidx/compose/ui/platform/AndroidUiDispatcher;)V
+HSPLandroidx/compose/ui/platform/AndroidUiFrameClock;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/AndroidUiFrameClock;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLandroidx/compose/ui/platform/AndroidUiFrameClock;->minusKey(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+HSPLandroidx/compose/ui/platform/AndroidUiFrameClock;->withFrameNanos(Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/AndroidViewConfiguration;-><init>(Landroid/view/ViewConfiguration;)V
+HSPLandroidx/compose/ui/platform/CalculateMatrixToWindowApi29;-><init>()V
+HSPLandroidx/compose/ui/platform/ComposableSingletons$Wrapper_androidKt;-><clinit>()V
+HSPLandroidx/compose/ui/platform/ComposeView;-><init>(Landroid/content/Context;)V
+HSPLandroidx/compose/ui/platform/ComposeView;->Content(Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/ui/platform/ComposeView;->getShouldCreateCompositionOnAttachedToWindow()Z
+HSPLandroidx/compose/ui/platform/ComposeView;->setContent(Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/ui/platform/CompositionLocalsKt;-><clinit>()V
+HSPLandroidx/compose/ui/platform/CompositionLocalsKt;->ProvideCommonCompositionLocals(Landroidx/compose/ui/node/Owner;Landroidx/compose/ui/platform/UriHandler;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/ui/platform/DisposableSaveableStateRegistry;-><init>(Landroidx/compose/runtime/saveable/SaveableStateRegistryImpl;Landroidx/compose/ui/platform/DisposableSaveableStateRegistry_androidKt$DisposableSaveableStateRegistry$1;)V
+HSPLandroidx/compose/ui/platform/DisposableSaveableStateRegistry;->canBeSaved(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/platform/DisposableSaveableStateRegistry;->consumeRestored(Ljava/lang/String;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/DisposableSaveableStateRegistry;->registerProvider(Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Landroidx/compose/runtime/saveable/SaveableStateRegistryImpl$registerProvider$3;
+HSPLandroidx/compose/ui/platform/DisposableSaveableStateRegistry_androidKt$DisposableSaveableStateRegistry$1;-><init>(ZLandroidx/savedstate/SavedStateRegistry;Ljava/lang/String;)V
+HSPLandroidx/compose/ui/platform/GlobalSnapshotManager$ensureStarted$1;-><init>(Lkotlinx/coroutines/channels/Channel;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/ui/platform/GlobalSnapshotManager$ensureStarted$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/ui/platform/GlobalSnapshotManager$ensureStarted$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/GlobalSnapshotManager;-><clinit>()V
+HSPLandroidx/compose/ui/platform/InspectableModifier;-><init>()V
+HSPLandroidx/compose/ui/platform/LayerMatrixCache;-><init>(Landroidx/compose/ui/text/SaversKt$ColorSaver$1;)V
+HSPLandroidx/compose/ui/platform/LayerMatrixCache;->calculateMatrix-GrdbGEg(Ljava/lang/Object;)[F
+HSPLandroidx/compose/ui/platform/LayerMatrixCache;->invalidate()V
+HSPLandroidx/compose/ui/platform/MotionDurationScaleImpl;-><init>()V
+HSPLandroidx/compose/ui/platform/MotionDurationScaleImpl;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/MotionDurationScaleImpl;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLandroidx/compose/ui/platform/MotionDurationScaleImpl;->getScaleFactor()F
+HSPLandroidx/compose/ui/platform/MotionDurationScaleImpl;->minusKey(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+HSPLandroidx/compose/ui/platform/OutlineResolver;-><init>(Landroidx/compose/ui/unit/Density;)V
+HSPLandroidx/compose/ui/platform/OutlineResolver;->getOutline()Landroid/graphics/Outline;
+HSPLandroidx/compose/ui/platform/OutlineResolver;->update(Landroidx/compose/ui/graphics/Shape;FZFLandroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/ui/unit/Density;)Z
+HSPLandroidx/compose/ui/platform/OutlineResolver;->updateCache()V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;-><init>()V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->discardDisplayList()V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->drawInto(Landroid/graphics/Canvas;)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getAlpha()F
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getClipToOutline()Z
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getElevation()F
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getHasDisplayList()Z
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getHeight()I
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getLeft()I
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getMatrix(Landroid/graphics/Matrix;)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getTop()I
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->getWidth()I
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->offsetLeftAndRight(I)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->offsetTopAndBottom(I)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setAlpha(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setAmbientShadowColor(I)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setCameraDistance(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setClipToBounds(Z)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setClipToOutline(Z)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setCompositingStrategy-aDBOjCE(I)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setElevation(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setHasOverlappingRendering()Z
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setOutline(Landroid/graphics/Outline;)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setPivotX(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setPivotY(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setPosition(IIII)Z
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setRenderEffect()V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setRotationX(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setRotationY(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setRotationZ(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setScaleX(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setScaleY(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setSpotShadowColor(I)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setTranslationX(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29;->setTranslationY(F)V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29VerificationHelper;-><clinit>()V
+HSPLandroidx/compose/ui/platform/RenderNodeApi29VerificationHelper;->setRenderEffect(Landroid/graphics/RenderNode;Landroidx/compose/ui/graphics/RenderEffect;)V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/node/LayoutNode$_foldedChildren$1;)V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->destroy()V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->drawLayer(Landroidx/compose/ui/graphics/Canvas;)V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->invalidate()V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->mapBounds(Landroidx/compose/ui/geometry/MutableRect;Z)V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->mapOffset-8S9VItk(JZ)J
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->move--gyyYBs(J)V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->resize-ozmzZPI(J)V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->reuseLayer(Landroidx/compose/ui/node/LayoutNode$_foldedChildren$1;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->setDirty(Z)V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->updateDisplayList()V
+HSPLandroidx/compose/ui/platform/RenderNodeLayer;->updateLayerProperties-dDxr-wY(FFFFFFFFFFJLandroidx/compose/ui/graphics/Shape;ZJJILandroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/ui/unit/Density;)V
+HSPLandroidx/compose/ui/platform/ViewLayer;-><clinit>()V
+HSPLandroidx/compose/ui/platform/WeakCache;-><init>()V
+HSPLandroidx/compose/ui/platform/WeakCache;-><init>(I)V
+HSPLandroidx/compose/ui/platform/WeakCache;-><init>(Landroidx/compose/ui/node/InnerNodeCoordinator;)V
+HSPLandroidx/compose/ui/platform/WeakCache;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/platform/WeakCache;->add(Landroidx/compose/ui/node/LayoutNode;Z)V
+HSPLandroidx/compose/ui/platform/WeakCache;->clearWeakReferences()V
+HSPLandroidx/compose/ui/platform/WeakCache;->get$1()Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WeakCache;->set(Ljava/lang/Object;)V
+HSPLandroidx/compose/ui/platform/WindowInfoImpl;-><clinit>()V
+HSPLandroidx/compose/ui/platform/WindowInfoImpl;-><init>()V
+HSPLandroidx/compose/ui/platform/WindowRecomposerPolicy$createAndInstallWindowRecomposer$unsetJob$1;-><init>(Landroidx/compose/runtime/Recomposer;Landroid/view/View;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/ui/platform/WindowRecomposerPolicy$createAndInstallWindowRecomposer$unsetJob$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/ui/platform/WindowRecomposerPolicy$createAndInstallWindowRecomposer$unsetJob$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WindowRecomposerPolicy;-><clinit>()V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$1;-><init>(Landroid/view/View;Landroidx/compose/runtime/Recomposer;)V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$1;->onViewAttachedToWindow(Landroid/view/View;)V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$WhenMappings;-><clinit>()V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1$1$1;-><init>(Lkotlinx/coroutines/flow/StateFlow;Landroidx/compose/ui/platform/MotionDurationScaleImpl;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1$1$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1$1$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1;-><init>(Lkotlin/jvm/internal/Ref$ObjectRef;Landroidx/compose/runtime/Recomposer;Landroidx/lifecycle/LifecycleOwner;Landroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2;Landroid/view/View;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2;-><init>(Lkotlinx/coroutines/internal/ContextScope;Landroidx/compose/runtime/PausableMonotonicFrameClock;Landroidx/compose/runtime/Recomposer;Lkotlin/jvm/internal/Ref$ObjectRef;Landroid/view/View;)V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$getAnimationScaleFlowFor$1$1$1;-><init>(Landroid/content/ContentResolver;Landroid/net/Uri;Landroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader$1;Lkotlinx/coroutines/channels/Channel;Landroid/content/Context;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$getAnimationScaleFlowFor$1$1$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$getAnimationScaleFlowFor$1$1$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt$getAnimationScaleFlowFor$1$1$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt;-><clinit>()V
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt;->access$getAnimationScaleFlowFor(Landroid/content/Context;)Lkotlinx/coroutines/flow/StateFlow;
+HSPLandroidx/compose/ui/platform/WindowRecomposer_androidKt;->getCompositionContext(Landroid/view/View;)Landroidx/compose/runtime/CompositionContext;
+HSPLandroidx/compose/ui/platform/WrappedComposition$setContent$1$1$1;-><init>(Landroidx/compose/ui/platform/WrappedComposition;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/compose/ui/platform/WrappedComposition$setContent$1$1$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLandroidx/compose/ui/platform/WrappedComposition$setContent$1$1$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WrappedComposition$setContent$1$1;-><init>(Landroidx/compose/ui/platform/WrappedComposition;Lkotlin/jvm/functions/Function2;I)V
+HSPLandroidx/compose/ui/platform/WrappedComposition$setContent$1$1;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/compose/ui/platform/WrappedComposition$setContent$1$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WrappedComposition$setContent$1;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/compose/ui/platform/WrappedComposition$setContent$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/platform/WrappedComposition;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;Landroidx/compose/runtime/CompositionImpl;)V
+HSPLandroidx/compose/ui/platform/WrappedComposition;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/compose/ui/platform/WrappedComposition;->setContent(Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/ui/platform/WrapperRenderNodeLayerHelperMethods;-><clinit>()V
+HSPLandroidx/compose/ui/platform/WrapperRenderNodeLayerHelperMethods;->onDescendantInvalidated(Landroidx/compose/ui/platform/AndroidComposeView;)V
+HSPLandroidx/compose/ui/platform/WrapperVerificationHelperMethods;-><clinit>()V
+HSPLandroidx/compose/ui/platform/WrapperVerificationHelperMethods;->attributeSourceResourceMap(Landroid/view/View;)Ljava/util/Map;
+HSPLandroidx/compose/ui/platform/Wrapper_androidKt;-><clinit>()V
+HSPLandroidx/compose/ui/platform/Wrapper_androidKt;->setContent(Landroidx/compose/ui/platform/AbstractComposeView;Landroidx/compose/runtime/CompositionContext;Landroidx/compose/runtime/internal/ComposableLambdaImpl;)Landroidx/compose/runtime/Composition;
+HSPLandroidx/compose/ui/res/ImageVectorCache;-><init>()V
+HSPLandroidx/compose/ui/semantics/AppendedSemanticsElement;-><init>(Lkotlin/jvm/functions/Function1;Z)V
+HSPLandroidx/compose/ui/semantics/AppendedSemanticsElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/semantics/AppendedSemanticsElement;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/semantics/AppendedSemanticsElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+HSPLandroidx/compose/ui/semantics/CollectionInfo;-><init>(II)V
+HSPLandroidx/compose/ui/semantics/CoreSemanticsModifierNode;-><init>(ZLkotlin/jvm/functions/Function1;)V
+HSPLandroidx/compose/ui/semantics/EmptySemanticsElement;-><clinit>()V
+HSPLandroidx/compose/ui/semantics/EmptySemanticsElement;-><init>()V
+HSPLandroidx/compose/ui/semantics/EmptySemanticsElement;->create()Landroidx/compose/ui/Modifier$Node;
+HSPLandroidx/compose/ui/semantics/ScrollAxisRange;-><init>(Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Z)V
+HSPLandroidx/compose/ui/semantics/SemanticsConfiguration;-><init>()V
+HSPLandroidx/compose/ui/semantics/SemanticsConfiguration;->contains(Landroidx/compose/ui/semantics/SemanticsPropertyKey;)Z
+HSPLandroidx/compose/ui/semantics/SemanticsModifierKt;-><clinit>()V
+HSPLandroidx/compose/ui/semantics/SemanticsNode;-><init>(Landroidx/compose/ui/Modifier$Node;ZLandroidx/compose/ui/node/LayoutNode;Landroidx/compose/ui/semantics/SemanticsConfiguration;)V
+HSPLandroidx/compose/ui/semantics/SemanticsNode;->fillOneLayerOfSemanticsWrappers(Landroidx/compose/ui/node/LayoutNode;Ljava/util/ArrayList;)V
+HSPLandroidx/compose/ui/semantics/SemanticsNode;->getChildren(ZZ)Ljava/util/List;
+HSPLandroidx/compose/ui/semantics/SemanticsNode;->getReplacedChildren$ui_release()Ljava/util/List;
+HSPLandroidx/compose/ui/semantics/SemanticsNode;->isMergingSemanticsOfDescendants()Z
+HSPLandroidx/compose/ui/semantics/SemanticsNode;->unmergedChildren$ui_release(Z)Ljava/util/List;
+HSPLandroidx/compose/ui/semantics/SemanticsOwner;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/semantics/SemanticsOwner;->getUnmergedRootSemanticsNode()Landroidx/compose/ui/semantics/SemanticsNode;
+HSPLandroidx/compose/ui/semantics/SemanticsProperties;-><clinit>()V
+HSPLandroidx/compose/ui/semantics/SemanticsPropertyKey;-><init>(Ljava/lang/String;)V
+HSPLandroidx/compose/ui/semantics/SemanticsPropertyKey;-><init>(Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V
+HSPLandroidx/compose/ui/text/AndroidParagraph;-><init>(Landroidx/compose/ui/text/platform/AndroidParagraphIntrinsics;IZJ)V
+HSPLandroidx/compose/ui/text/AndroidParagraph;->constructTextLayout(IILandroid/text/TextUtils$TruncateAt;IIIII)Landroidx/compose/ui/text/android/TextLayout;
+HSPLandroidx/compose/ui/text/AndroidParagraph;->getHeight()F
+HSPLandroidx/compose/ui/text/AndroidParagraph;->getWidth()F
+HSPLandroidx/compose/ui/text/AndroidParagraph;->paint(Landroidx/compose/ui/graphics/Canvas;)V
+HSPLandroidx/compose/ui/text/AndroidParagraph;->paint-LG529CI(Landroidx/compose/ui/graphics/Canvas;JLandroidx/compose/ui/graphics/Shadow;Landroidx/compose/ui/text/style/TextDecoration;Lkotlin/ResultKt;I)V
+HSPLandroidx/compose/ui/text/AnnotatedString$Range;-><init>(IILjava/lang/Object;)V
+HSPLandroidx/compose/ui/text/AnnotatedString$Range;-><init>(Ljava/lang/Object;IILjava/lang/String;)V
+HSPLandroidx/compose/ui/text/AnnotatedString;-><init>(Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;)V
+HSPLandroidx/compose/ui/text/AnnotatedString;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/AnnotatedStringKt;-><clinit>()V
+HSPLandroidx/compose/ui/text/EmojiSupportMatch;-><init>(I)V
+HSPLandroidx/compose/ui/text/MultiParagraph;->paint-LG529CI$default(Landroidx/compose/ui/text/MultiParagraph;Landroidx/compose/ui/graphics/Canvas;JLandroidx/compose/ui/graphics/Shadow;Landroidx/compose/ui/text/style/TextDecoration;Lkotlin/ResultKt;)V
+HSPLandroidx/compose/ui/text/MultiParagraphIntrinsics$maxIntrinsicWidth$2;-><init>(Landroidx/compose/ui/text/MultiParagraphIntrinsics;I)V
+HSPLandroidx/compose/ui/text/MultiParagraphIntrinsics$maxIntrinsicWidth$2;->invoke$1()Ljava/lang/Float;
+HSPLandroidx/compose/ui/text/MultiParagraphIntrinsics$maxIntrinsicWidth$2;->invoke()Ljava/lang/Object;
+HSPLandroidx/compose/ui/text/MultiParagraphIntrinsics;-><init>(Landroidx/compose/ui/text/AnnotatedString;Landroidx/compose/ui/text/TextStyle;Ljava/util/List;Landroidx/compose/ui/unit/Density;Landroidx/compose/ui/text/font/FontFamily$Resolver;)V
+HSPLandroidx/compose/ui/text/MultiParagraphIntrinsics;->getHasStaleResolvedFonts()Z
+HSPLandroidx/compose/ui/text/MultiParagraphIntrinsics;->getMaxIntrinsicWidth()F
+HSPLandroidx/compose/ui/text/ParagraphInfo;-><init>(Landroidx/compose/ui/text/AndroidParagraph;IIIIFF)V
+HSPLandroidx/compose/ui/text/ParagraphIntrinsicInfo;-><init>(Landroidx/compose/ui/text/platform/AndroidParagraphIntrinsics;II)V
+HSPLandroidx/compose/ui/text/ParagraphStyle;-><init>(Landroidx/compose/ui/text/style/TextAlign;Landroidx/compose/ui/text/style/TextDirection;JLandroidx/compose/ui/text/style/TextIndent;Landroidx/compose/ui/text/PlatformParagraphStyle;Landroidx/compose/ui/text/style/LineHeightStyle;Landroidx/compose/ui/text/style/LineBreak;Landroidx/compose/ui/text/style/Hyphens;Landroidx/compose/ui/text/style/TextMotion;)V
+HSPLandroidx/compose/ui/text/ParagraphStyle;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/ParagraphStyle;->merge(Landroidx/compose/ui/text/ParagraphStyle;)Landroidx/compose/ui/text/ParagraphStyle;
+HSPLandroidx/compose/ui/text/ParagraphStyleKt;-><clinit>()V
+HSPLandroidx/compose/ui/text/ParagraphStyleKt;->fastMerge-HtYhynw(Landroidx/compose/ui/text/ParagraphStyle;Landroidx/compose/ui/text/style/TextAlign;Landroidx/compose/ui/text/style/TextDirection;JLandroidx/compose/ui/text/style/TextIndent;Landroidx/compose/ui/text/PlatformParagraphStyle;Landroidx/compose/ui/text/style/LineHeightStyle;Landroidx/compose/ui/text/style/LineBreak;Landroidx/compose/ui/text/style/Hyphens;Landroidx/compose/ui/text/style/TextMotion;)Landroidx/compose/ui/text/ParagraphStyle;
+HSPLandroidx/compose/ui/text/PlatformParagraphStyle;-><init>(I)V
+HSPLandroidx/compose/ui/text/PlatformParagraphStyle;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/PlatformTextStyle;-><init>(Landroidx/compose/ui/text/PlatformParagraphStyle;)V
+HSPLandroidx/compose/ui/text/SaversKt$ColorSaver$1;-><clinit>()V
+HSPLandroidx/compose/ui/text/SaversKt$ColorSaver$1;-><init>(I)V
+HSPLandroidx/compose/ui/text/SaversKt$ColorSaver$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/text/SaversKt$ColorSaver$2;-><clinit>()V
+HSPLandroidx/compose/ui/text/SaversKt$ColorSaver$2;-><init>(I)V
+HSPLandroidx/compose/ui/text/SaversKt$ColorSaver$2;->invoke(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/compose/ui/text/SaversKt$ColorSaver$2;->invoke(Ljava/lang/Object;)Ljava/lang/Boolean;
+HSPLandroidx/compose/ui/text/SaversKt$ColorSaver$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/text/SpanStyle;-><init>(JJLandroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontSynthesis;Landroidx/compose/ui/text/font/FontFamily;Ljava/lang/String;JLandroidx/compose/ui/text/style/BaselineShift;Landroidx/compose/ui/text/style/TextGeometricTransform;Landroidx/compose/ui/text/intl/LocaleList;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/graphics/Shadow;I)V
+HSPLandroidx/compose/ui/text/SpanStyle;-><init>(JJLandroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontSynthesis;Landroidx/compose/ui/text/font/FontFamily;Ljava/lang/String;JLandroidx/compose/ui/text/style/BaselineShift;Landroidx/compose/ui/text/style/TextGeometricTransform;Landroidx/compose/ui/text/intl/LocaleList;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/graphics/Shadow;Lkotlin/ResultKt;)V
+HSPLandroidx/compose/ui/text/SpanStyle;-><init>(Landroidx/compose/ui/text/style/TextForegroundStyle;JLandroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontSynthesis;Landroidx/compose/ui/text/font/FontFamily;Ljava/lang/String;JLandroidx/compose/ui/text/style/BaselineShift;Landroidx/compose/ui/text/style/TextGeometricTransform;Landroidx/compose/ui/text/intl/LocaleList;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/graphics/Shadow;Lkotlin/ResultKt;)V
+HSPLandroidx/compose/ui/text/SpanStyle;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/SpanStyle;->getColor-0d7_KjU()J
+HSPLandroidx/compose/ui/text/SpanStyle;->hasSameLayoutAffectingAttributes$ui_text_release(Landroidx/compose/ui/text/SpanStyle;)Z
+HSPLandroidx/compose/ui/text/SpanStyle;->hasSameNonLayoutAttributes$ui_text_release(Landroidx/compose/ui/text/SpanStyle;)Z
+HSPLandroidx/compose/ui/text/SpanStyle;->merge(Landroidx/compose/ui/text/SpanStyle;)Landroidx/compose/ui/text/SpanStyle;
+HSPLandroidx/compose/ui/text/SpanStyleKt;-><clinit>()V
+HSPLandroidx/compose/ui/text/SpanStyleKt;->fastMerge-dSHsh3o(Landroidx/compose/ui/text/SpanStyle;JLandroidx/compose/ui/graphics/Brush;FJLandroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontSynthesis;Landroidx/compose/ui/text/font/FontFamily;Ljava/lang/String;JLandroidx/compose/ui/text/style/BaselineShift;Landroidx/compose/ui/text/style/TextGeometricTransform;Landroidx/compose/ui/text/intl/LocaleList;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/graphics/Shadow;Lkotlin/ResultKt;)Landroidx/compose/ui/text/SpanStyle;
+HSPLandroidx/compose/ui/text/TextLayoutInput;-><init>(Landroidx/compose/ui/text/AnnotatedString;Landroidx/compose/ui/text/TextStyle;Ljava/util/List;IZILandroidx/compose/ui/unit/Density;Landroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/ui/text/font/FontFamily$Resolver;J)V
+HSPLandroidx/compose/ui/text/TextLayoutResult;-><init>(Landroidx/compose/ui/text/TextLayoutInput;Landroidx/compose/ui/text/MultiParagraph;J)V
+HSPLandroidx/compose/ui/text/TextRange;-><clinit>()V
+HSPLandroidx/compose/ui/text/TextRange;->getEnd-impl(J)I
+HSPLandroidx/compose/ui/text/TextStyle;-><clinit>()V
+HSPLandroidx/compose/ui/text/TextStyle;-><init>(JJLandroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontFamily;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/text/style/TextAlign;JI)V
+HSPLandroidx/compose/ui/text/TextStyle;-><init>(JLandroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/DefaultFontFamily;I)V
+HSPLandroidx/compose/ui/text/TextStyle;-><init>(Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/ParagraphStyle;)V
+HSPLandroidx/compose/ui/text/TextStyle;-><init>(Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/ui/text/ParagraphStyle;Landroidx/compose/ui/text/PlatformTextStyle;)V
+HSPLandroidx/compose/ui/text/TextStyle;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/TextStyle;->getColor-0d7_KjU()J
+HSPLandroidx/compose/ui/text/TextStyle;->merge(Landroidx/compose/ui/text/TextStyle;)Landroidx/compose/ui/text/TextStyle;
+HSPLandroidx/compose/ui/text/TextStyle;->merge-Z1GrekI$default(IJJJJLandroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/font/FontFamily;Landroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/style/TextAlign;Landroidx/compose/ui/text/style/TextDecoration;)Landroidx/compose/ui/text/TextStyle;
+HSPLandroidx/compose/ui/text/android/BoringLayoutFactoryDefault;->create(Ljava/lang/CharSequence;Landroid/text/TextPaint;ILandroid/text/Layout$Alignment;FFLandroid/text/BoringLayout$Metrics;ZLandroid/text/TextUtils$TruncateAt;I)Landroid/text/BoringLayout;
+HSPLandroidx/compose/ui/text/android/BoringLayoutFactoryDefault;->isBoring(Ljava/lang/CharSequence;Landroid/text/TextPaint;Landroid/text/TextDirectionHeuristic;)Landroid/text/BoringLayout$Metrics;
+HSPLandroidx/compose/ui/text/android/LayoutIntrinsics;-><init>(Ljava/lang/CharSequence;Landroidx/compose/ui/text/platform/AndroidTextPaint;I)V
+HSPLandroidx/compose/ui/text/android/LayoutIntrinsics;->getBoringMetrics()Landroid/text/BoringLayout$Metrics;
+HSPLandroidx/compose/ui/text/android/LayoutIntrinsics;->getMaxIntrinsicWidth()F
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m$1(Landroid/graphics/RenderNode;)F
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m$1(Landroid/graphics/RenderNode;)Z
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m$1(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m$2(Landroid/graphics/RenderNode;)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m$2(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m$3(Landroid/graphics/RenderNode;)I
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m$3(Landroid/graphics/RenderNode;)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m()Landroid/graphics/RenderNode;
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/Paint;Ljava/lang/CharSequence;IILandroid/graphics/Rect;)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)F
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)I
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)Landroid/graphics/RecordingCanvas;
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;)Z
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;F)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;I)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;IIII)Z
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;Landroid/graphics/Matrix;)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;Landroid/graphics/Outline;)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/graphics/RenderNode;Z)V
+HSPLandroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;->m(Landroid/view/View;)Ljava/util/Map;
+HSPLandroidx/compose/ui/text/android/Paint29;->getTextBounds(Landroid/graphics/Paint;Ljava/lang/CharSequence;IILandroid/graphics/Rect;)V
+HSPLandroidx/compose/ui/text/android/StaticLayoutFactory23;->create(Landroidx/compose/ui/text/android/StaticLayoutParams;)Landroid/text/StaticLayout;
+HSPLandroidx/compose/ui/text/android/StaticLayoutFactory26;->setJustificationMode(Landroid/text/StaticLayout$Builder;I)V
+HSPLandroidx/compose/ui/text/android/StaticLayoutFactory28;->setUseLineSpacingFromFallbacks(Landroid/text/StaticLayout$Builder;Z)V
+HSPLandroidx/compose/ui/text/android/StaticLayoutParams;-><init>(Ljava/lang/CharSequence;IILandroidx/compose/ui/text/platform/AndroidTextPaint;ILandroid/text/TextDirectionHeuristic;Landroid/text/Layout$Alignment;ILandroid/text/TextUtils$TruncateAt;IFFIZZIIII[I[I)V
+HSPLandroidx/compose/ui/text/android/TextAlignmentAdapter;-><clinit>()V
+HSPLandroidx/compose/ui/text/android/TextAndroidCanvas;->drawText(Ljava/lang/String;FFLandroid/graphics/Paint;)V
+HSPLandroidx/compose/ui/text/android/TextAndroidCanvas;->drawTextRun(Ljava/lang/CharSequence;IIIIFFZLandroid/graphics/Paint;)V
+HSPLandroidx/compose/ui/text/android/TextAndroidCanvas;->getClipBounds(Landroid/graphics/Rect;)Z
+HSPLandroidx/compose/ui/text/android/TextLayout;-><init>(Ljava/lang/CharSequence;FLandroidx/compose/ui/text/platform/AndroidTextPaint;ILandroid/text/TextUtils$TruncateAt;IZIIIIIILandroidx/compose/ui/text/android/LayoutIntrinsics;)V
+HSPLandroidx/compose/ui/text/android/TextLayout;->getHeight()I
+HSPLandroidx/compose/ui/text/android/TextLayout;->getLineBaseline(I)F
+HSPLandroidx/compose/ui/text/android/TextLayout;->getText()Ljava/lang/CharSequence;
+HSPLandroidx/compose/ui/text/android/TextLayoutKt;-><clinit>()V
+HSPLandroidx/compose/ui/text/android/TextLayoutKt;->getTextDirectionHeuristic(I)Landroid/text/TextDirectionHeuristic;
+HSPLandroidx/compose/ui/text/android/style/LineHeightStyleSpan;-><init>(FIZZF)V
+HSPLandroidx/compose/ui/text/android/style/LineHeightStyleSpan;->chooseHeight(Ljava/lang/CharSequence;IIIILandroid/graphics/Paint$FontMetricsInt;)V
+HSPLandroidx/compose/ui/text/caches/LruCache;-><init>()V
+HSPLandroidx/compose/ui/text/caches/LruCache;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/text/caches/LruCache;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/text/caches/LruCache;->size()I
+HSPLandroidx/compose/ui/text/caches/SimpleArrayMap;-><init>()V
+HSPLandroidx/compose/ui/text/font/AndroidFontResolveInterceptor;-><init>(I)V
+HSPLandroidx/compose/ui/text/font/AndroidFontResolveInterceptor;->interceptFontWeight(Landroidx/compose/ui/text/font/FontWeight;)Landroidx/compose/ui/text/font/FontWeight;
+HSPLandroidx/compose/ui/text/font/AsyncTypefaceCache;-><init>()V
+HSPLandroidx/compose/ui/text/font/FontFamily;-><clinit>()V
+HSPLandroidx/compose/ui/text/font/FontFamilyResolverImpl;-><init>(Landroidx/compose/ui/unit/Dp$Companion;Landroidx/compose/ui/text/font/AndroidFontResolveInterceptor;)V
+HSPLandroidx/compose/ui/text/font/FontFamilyResolverImpl;->resolve(Landroidx/compose/ui/text/font/TypefaceRequest;)Landroidx/compose/ui/text/font/TypefaceResult;
+HSPLandroidx/compose/ui/text/font/FontFamilyResolverImpl;->resolve-DPcqOEQ(Landroidx/compose/ui/text/font/FontFamily;Landroidx/compose/ui/text/font/FontWeight;II)Landroidx/compose/ui/text/font/TypefaceResult;
+HSPLandroidx/compose/ui/text/font/FontFamilyResolverKt;-><clinit>()V
+HSPLandroidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter$special$$inlined$CoroutineExceptionHandler$1;-><init>()V
+HSPLandroidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter;-><clinit>()V
+HSPLandroidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter;-><init>(Landroidx/compose/ui/text/font/AsyncTypefaceCache;)V
+HSPLandroidx/compose/ui/text/font/FontStyle;-><init>(I)V
+HSPLandroidx/compose/ui/text/font/FontSynthesis;-><init>(I)V
+HSPLandroidx/compose/ui/text/font/FontWeight;-><clinit>()V
+HSPLandroidx/compose/ui/text/font/FontWeight;-><init>(I)V
+HSPLandroidx/compose/ui/text/font/FontWeight;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/font/PlatformResolveInterceptor$Companion;-><clinit>()V
+HSPLandroidx/compose/ui/text/font/PlatformResolveInterceptor;-><clinit>()V
+HSPLandroidx/compose/ui/text/font/TypefaceRequest;-><init>(Landroidx/compose/ui/text/font/FontFamily;Landroidx/compose/ui/text/font/FontWeight;IILjava/lang/Object;)V
+HSPLandroidx/compose/ui/text/font/TypefaceRequest;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/font/TypefaceRequest;->hashCode()I
+HSPLandroidx/compose/ui/text/font/TypefaceResult$Immutable;-><init>(Ljava/lang/Object;Z)V
+HSPLandroidx/compose/ui/text/input/InputMethodManagerImpl;-><init>(Landroid/view/View;)V
+HSPLandroidx/compose/ui/text/input/TextFieldValue;-><clinit>()V
+HSPLandroidx/compose/ui/text/input/TextFieldValue;-><init>(Landroidx/compose/ui/text/AnnotatedString;JLandroidx/compose/ui/text/TextRange;)V
+HSPLandroidx/compose/ui/text/input/TextInputService;-><init>()V
+HSPLandroidx/compose/ui/text/input/TextInputServiceAndroid;-><init>(Landroid/view/View;Landroidx/compose/ui/input/pointer/PositionCalculator;)V
+HSPLandroidx/compose/ui/text/input/TextInputServiceAndroid_androidKt$$ExternalSyntheticLambda0;-><init>(Ljava/lang/Runnable;I)V
+HSPLandroidx/compose/ui/text/input/TextInputServiceAndroid_androidKt$$ExternalSyntheticLambda0;->doFrame(J)V
+HSPLandroidx/compose/ui/text/intl/AndroidLocale;-><init>(Ljava/util/Locale;)V
+HSPLandroidx/compose/ui/text/intl/AndroidLocaleDelegateAPI24;-><init>()V
+HSPLandroidx/compose/ui/text/intl/AndroidLocaleDelegateAPI24;->getCurrent()Landroidx/compose/ui/text/intl/LocaleList;
+HSPLandroidx/compose/ui/text/intl/Locale;-><init>(Landroidx/compose/ui/text/intl/AndroidLocale;)V
+HSPLandroidx/compose/ui/text/intl/LocaleList;-><init>(Ljava/util/List;)V
+HSPLandroidx/compose/ui/text/intl/LocaleList;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/intl/PlatformLocaleKt;-><clinit>()V
+HSPLandroidx/compose/ui/text/platform/AndroidParagraphHelper_androidKt;-><clinit>()V
+HSPLandroidx/compose/ui/text/platform/AndroidParagraphIntrinsics$resolveTypeface$1;-><init>(Landroidx/compose/ui/text/platform/AndroidParagraphIntrinsics;)V
+HSPLandroidx/compose/ui/text/platform/AndroidParagraphIntrinsics;-><init>(Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/font/FontFamily$Resolver;Landroidx/compose/ui/unit/Density;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)V
+HSPLandroidx/compose/ui/text/platform/AndroidParagraphIntrinsics;->getHasStaleResolvedFonts()Z
+HSPLandroidx/compose/ui/text/platform/AndroidParagraphIntrinsics;->getMaxIntrinsicWidth()F
+HSPLandroidx/compose/ui/text/platform/AndroidTextPaint;-><init>(F)V
+HSPLandroidx/compose/ui/text/platform/AndroidTextPaint;->setBrush-12SF9DM(Landroidx/compose/ui/graphics/Brush;JF)V
+HSPLandroidx/compose/ui/text/platform/AndroidTextPaint;->setDrawStyle(Lkotlin/ResultKt;)V
+HSPLandroidx/compose/ui/text/platform/AndroidTextPaint;->setShadow(Landroidx/compose/ui/graphics/Shadow;)V
+HSPLandroidx/compose/ui/text/platform/AndroidTextPaint;->setTextDecoration(Landroidx/compose/ui/text/style/TextDecoration;)V
+HSPLandroidx/compose/ui/text/platform/DefaultImpl$getFontLoadState$initCallback$1;-><init>(Landroidx/compose/runtime/ParcelableSnapshotMutableState;Landroidx/compose/ui/text/platform/DefaultImpl;)V
+HSPLandroidx/compose/ui/text/platform/DefaultImpl;-><init>()V
+HSPLandroidx/compose/ui/text/platform/DefaultImpl;->getFontLoadState()Landroidx/compose/runtime/State;
+HSPLandroidx/compose/ui/text/platform/EmojiCompatStatus;-><clinit>()V
+HSPLandroidx/compose/ui/text/platform/ImmutableBool;-><init>(Z)V
+HSPLandroidx/compose/ui/text/platform/ImmutableBool;->getValue()Ljava/lang/Object;
+HSPLandroidx/compose/ui/text/platform/URLSpanCache;-><init>()V
+HSPLandroidx/compose/ui/text/platform/extensions/LocaleListHelperMethods;-><clinit>()V
+HSPLandroidx/compose/ui/text/style/BaselineShift;-><init>(F)V
+HSPLandroidx/compose/ui/text/style/ColorStyle;-><init>(J)V
+HSPLandroidx/compose/ui/text/style/ColorStyle;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/style/ColorStyle;->getAlpha()F
+HSPLandroidx/compose/ui/text/style/ColorStyle;->getBrush()Landroidx/compose/ui/graphics/Brush;
+HSPLandroidx/compose/ui/text/style/ColorStyle;->getColor-0d7_KjU()J
+HSPLandroidx/compose/ui/text/style/Hyphens;-><init>(I)V
+HSPLandroidx/compose/ui/text/style/Hyphens;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/style/LineBreak$Strategy;-><init>(I)V
+HSPLandroidx/compose/ui/text/style/LineBreak$Strictness;-><init>(I)V
+HSPLandroidx/compose/ui/text/style/LineBreak$WordBreak;-><init>(I)V
+HSPLandroidx/compose/ui/text/style/LineBreak;-><init>(I)V
+HSPLandroidx/compose/ui/text/style/LineBreak;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/style/LineHeightStyle$Alignment;-><clinit>()V
+HSPLandroidx/compose/ui/text/style/LineHeightStyle$Alignment;->constructor-impl(F)V
+HSPLandroidx/compose/ui/text/style/LineHeightStyle;-><clinit>()V
+HSPLandroidx/compose/ui/text/style/LineHeightStyle;-><init>(F)V
+HSPLandroidx/compose/ui/text/style/TextAlign;-><init>(I)V
+HSPLandroidx/compose/ui/text/style/TextAlign;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/style/TextDecoration;-><clinit>()V
+HSPLandroidx/compose/ui/text/style/TextDecoration;-><init>(I)V
+HSPLandroidx/compose/ui/text/style/TextDecoration;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/style/TextDirection;-><init>(I)V
+HSPLandroidx/compose/ui/text/style/TextDirection;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/style/TextForegroundStyle$Unspecified;-><clinit>()V
+HSPLandroidx/compose/ui/text/style/TextForegroundStyle$Unspecified;->getAlpha()F
+HSPLandroidx/compose/ui/text/style/TextForegroundStyle$Unspecified;->getBrush()Landroidx/compose/ui/graphics/Brush;
+HSPLandroidx/compose/ui/text/style/TextForegroundStyle$Unspecified;->getColor-0d7_KjU()J
+HSPLandroidx/compose/ui/text/style/TextGeometricTransform;-><clinit>()V
+HSPLandroidx/compose/ui/text/style/TextGeometricTransform;-><init>(FF)V
+HSPLandroidx/compose/ui/text/style/TextGeometricTransform;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/style/TextIndent;-><clinit>()V
+HSPLandroidx/compose/ui/text/style/TextIndent;-><init>(JJ)V
+HSPLandroidx/compose/ui/text/style/TextIndent;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/text/style/TextMotion;-><clinit>()V
+HSPLandroidx/compose/ui/text/style/TextMotion;-><init>(IZ)V
+HSPLandroidx/compose/ui/text/style/TextMotion;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/unit/Constraints;-><clinit>()V
+HSPLandroidx/compose/ui/unit/Constraints;-><init>(J)V
+HSPLandroidx/compose/ui/unit/Constraints;->copy-Zbe2FdA$default(JIIIII)J
+HSPLandroidx/compose/ui/unit/Constraints;->equals-impl0(JJ)Z
+HSPLandroidx/compose/ui/unit/Constraints;->getHasBoundedHeight-impl(J)Z
+HSPLandroidx/compose/ui/unit/Constraints;->getHasBoundedWidth-impl(J)Z
+HSPLandroidx/compose/ui/unit/Constraints;->getHasFixedHeight-impl(J)Z
+HSPLandroidx/compose/ui/unit/Constraints;->getHasFixedWidth-impl(J)Z
+HSPLandroidx/compose/ui/unit/Constraints;->getMaxHeight-impl(J)I
+HSPLandroidx/compose/ui/unit/Constraints;->getMaxWidth-impl(J)I
+HSPLandroidx/compose/ui/unit/Constraints;->getMinHeight-impl(J)I
+HSPLandroidx/compose/ui/unit/Constraints;->getMinWidth-impl(J)I
+HSPLandroidx/compose/ui/unit/Density;->roundToPx-0680j_4(F)I
+HSPLandroidx/compose/ui/unit/Density;->toDp-u2uoSUM(I)F
+HSPLandroidx/compose/ui/unit/Density;->toPx--R2X_6o(J)F
+HSPLandroidx/compose/ui/unit/Density;->toPx-0680j_4(F)F
+HSPLandroidx/compose/ui/unit/DensityImpl;-><init>(FF)V
+HSPLandroidx/compose/ui/unit/DensityImpl;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/unit/DensityImpl;->getDensity()F
+HSPLandroidx/compose/ui/unit/DensityImpl;->getFontScale()F
+HSPLandroidx/compose/ui/unit/Dp$Companion;-><clinit>()V
+HSPLandroidx/compose/ui/unit/Dp$Companion;-><init>(Landroid/content/Context;)V
+HSPLandroidx/compose/ui/unit/Dp$Companion;->access$getIsShowingLayoutBounds()Z
+HSPLandroidx/compose/ui/unit/Dp$Companion;->area([F)F
+HSPLandroidx/compose/ui/unit/Dp$Companion;->bitsNeedForSize(I)I
+HSPLandroidx/compose/ui/unit/Dp$Companion;->createAndroidTypefaceApi28-RetOiIg(Ljava/lang/String;Landroidx/compose/ui/text/font/FontWeight;I)Landroid/graphics/Typeface;
+HSPLandroidx/compose/ui/unit/Dp$Companion;->createConstraints-Zbe2FdA$ui_unit_release(IIII)J
+HSPLandroidx/compose/ui/unit/Dp$Companion;->fixed-JhjzzOo(II)J
+HSPLandroidx/compose/ui/unit/Dp$Companion;->observe(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
+HSPLandroidx/compose/ui/unit/Dp;-><init>(F)V
+HSPLandroidx/compose/ui/unit/Dp;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/unit/Dp;->equals-impl0(FF)Z
+HSPLandroidx/compose/ui/unit/DpOffset;-><clinit>()V
+HSPLandroidx/compose/ui/unit/DpRect;-><init>(FFFF)V
+HSPLandroidx/compose/ui/unit/DpRect;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/compose/ui/unit/IntOffset;-><clinit>()V
+HSPLandroidx/compose/ui/unit/IntOffset;-><init>(J)V
+HSPLandroidx/compose/ui/unit/IntOffset;->getY-impl(J)I
+HSPLandroidx/compose/ui/unit/IntSize;-><init>(J)V
+HSPLandroidx/compose/ui/unit/IntSize;->equals-impl0(JJ)Z
+HSPLandroidx/compose/ui/unit/IntSize;->getHeight-impl(J)I
+HSPLandroidx/compose/ui/unit/LayoutDirection;-><clinit>()V
+HSPLandroidx/compose/ui/unit/LayoutDirection;-><init>(ILjava/lang/String;)V
+HSPLandroidx/compose/ui/unit/TextUnit;-><clinit>()V
+HSPLandroidx/compose/ui/unit/TextUnit;->equals-impl0(JJ)Z
+HSPLandroidx/compose/ui/unit/TextUnit;->getType-UIouoOA(J)J
+HSPLandroidx/compose/ui/unit/TextUnit;->getValue-impl(J)F
+HSPLandroidx/compose/ui/unit/TextUnitType;-><init>(J)V
+HSPLandroidx/compose/ui/unit/TextUnitType;->equals-impl0(JJ)Z
+HSPLandroidx/core/app/ComponentActivity;-><init>()V
+HSPLandroidx/core/app/ComponentActivity;->dispatchKeyEvent(Landroid/view/KeyEvent;)Z
+HSPLandroidx/core/app/ComponentActivity;->onCreate(Landroid/os/Bundle;)V
+HSPLandroidx/core/app/ComponentActivity;->superDispatchKeyEvent(Landroid/view/KeyEvent;)Z
+HSPLandroidx/core/app/CoreComponentFactory;-><init>()V
+HSPLandroidx/core/app/CoreComponentFactory;->instantiateActivity(Ljava/lang/ClassLoader;Ljava/lang/String;Landroid/content/Intent;)Landroid/app/Activity;
+HSPLandroidx/core/app/CoreComponentFactory;->instantiateApplication(Ljava/lang/ClassLoader;Ljava/lang/String;)Landroid/app/Application;
+HSPLandroidx/core/app/CoreComponentFactory;->instantiateProvider(Ljava/lang/ClassLoader;Ljava/lang/String;)Landroid/content/ContentProvider;
+HSPLandroidx/core/content/res/ComplexColorCompat;-><init>()V
+HSPLandroidx/core/content/res/ComplexColorCompat;-><init>(I)V
+HSPLandroidx/core/content/res/ComplexColorCompat;-><init>(Ljava/lang/Object;)V
+HSPLandroidx/core/content/res/ComplexColorCompat;->find(Ljava/lang/Object;)I
+HSPLandroidx/core/content/res/ComplexColorCompat;->get(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/core/content/res/ComplexColorCompat;->set(Ljava/lang/Object;Ljava/lang/Object;)V
+HSPLandroidx/core/graphics/TypefaceCompat;-><clinit>()V
+HSPLandroidx/core/graphics/TypefaceCompatApi29Impl;-><init>()V
+HSPLandroidx/core/graphics/TypefaceCompatApi29Impl;->createFromFontInfo(Landroid/content/Context;[Landroidx/core/provider/FontsContractCompat$FontInfo;I)Landroid/graphics/Typeface;
+HSPLandroidx/core/graphics/TypefaceCompatApi29Impl;->findBaseFont(Landroid/graphics/fonts/FontFamily;I)Landroid/graphics/fonts/Font;
+HSPLandroidx/core/graphics/TypefaceCompatApi29Impl;->getMatchScore(Landroid/graphics/fonts/FontStyle;Landroid/graphics/fonts/FontStyle;)I
+HSPLandroidx/core/graphics/TypefaceCompatUtil$Api19Impl;->openFileDescriptor(Landroid/content/ContentResolver;Landroid/net/Uri;Ljava/lang/String;Landroid/os/CancellationSignal;)Landroid/os/ParcelFileDescriptor;
+HSPLandroidx/core/os/BuildCompat$Extensions30Impl$$ExternalSyntheticApiModelOutline0;->m$1()I
+HSPLandroidx/core/os/BuildCompat$Extensions30Impl$$ExternalSyntheticApiModelOutline0;->m$2()I
+HSPLandroidx/core/os/BuildCompat$Extensions30Impl$$ExternalSyntheticApiModelOutline0;->m$3()I
+HSPLandroidx/core/os/BuildCompat$Extensions30Impl$$ExternalSyntheticApiModelOutline0;->m()I
+HSPLandroidx/core/os/BuildCompat$Extensions30Impl;-><clinit>()V
+HSPLandroidx/core/os/BuildCompat;-><clinit>()V
+HSPLandroidx/core/os/BuildCompat;->isAtLeastT()Z
+HSPLandroidx/core/os/TraceCompat$Api18Impl;->beginSection(Ljava/lang/String;)V
+HSPLandroidx/core/os/TraceCompat$Api18Impl;->endSection()V
+HSPLandroidx/core/os/TraceCompat;-><clinit>()V
+HSPLandroidx/core/provider/CallbackWithHandler$2;-><init>(ILjava/util/ArrayList;)V
+HSPLandroidx/core/provider/CallbackWithHandler$2;-><init>(Ljava/util/List;ILjava/lang/Throwable;)V
+HSPLandroidx/core/provider/CallbackWithHandler$2;->run()V
+HSPLandroidx/core/provider/FontProvider$Api16Impl;->query(Landroid/content/ContentResolver;Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)Landroid/database/Cursor;
+HSPLandroidx/core/provider/FontRequest;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V
+HSPLandroidx/core/provider/FontsContractCompat$FontInfo;-><init>(Landroid/net/Uri;IIZI)V
+HSPLandroidx/core/view/AccessibilityDelegateCompat$AccessibilityDelegateAdapter;-><init>(Landroidx/core/view/AccessibilityDelegateCompat;)V
+HSPLandroidx/core/view/AccessibilityDelegateCompat$AccessibilityDelegateAdapter;->dispatchPopulateAccessibilityEvent(Landroid/view/View;Landroid/view/accessibility/AccessibilityEvent;)Z
+HSPLandroidx/core/view/AccessibilityDelegateCompat$AccessibilityDelegateAdapter;->getAccessibilityNodeProvider(Landroid/view/View;)Landroid/view/accessibility/AccessibilityNodeProvider;
+HSPLandroidx/core/view/AccessibilityDelegateCompat$AccessibilityDelegateAdapter;->onInitializeAccessibilityEvent(Landroid/view/View;Landroid/view/accessibility/AccessibilityEvent;)V
+HSPLandroidx/core/view/AccessibilityDelegateCompat$AccessibilityDelegateAdapter;->onPopulateAccessibilityEvent(Landroid/view/View;Landroid/view/accessibility/AccessibilityEvent;)V
+HSPLandroidx/core/view/AccessibilityDelegateCompat$AccessibilityDelegateAdapter;->sendAccessibilityEvent(Landroid/view/View;I)V
+HSPLandroidx/core/view/AccessibilityDelegateCompat$AccessibilityDelegateAdapter;->sendAccessibilityEventUnchecked(Landroid/view/View;Landroid/view/accessibility/AccessibilityEvent;)V
+HSPLandroidx/core/view/AccessibilityDelegateCompat;-><clinit>()V
+HSPLandroidx/core/view/AccessibilityDelegateCompat;-><init>()V
+HSPLandroidx/core/view/MenuHostHelper;-><init>(Landroidx/activity/ComponentActivity$$ExternalSyntheticLambda0;)V
+HSPLandroidx/core/view/MenuHostHelper;-><init>(Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)V
+HSPLandroidx/core/view/ViewCompat$$ExternalSyntheticLambda0;-><init>(I)V
+HSPLandroidx/core/view/ViewCompat$$ExternalSyntheticLambda0;->invoke(D)D
+HSPLandroidx/core/view/ViewCompat$Api29Impl;->getContentCaptureSession(Landroid/view/View;)Landroid/view/contentcapture/ContentCaptureSession;
+HSPLandroidx/core/view/ViewCompat$Api30Impl;->setImportantForContentCapture(Landroid/view/View;I)V
+HSPLandroidx/core/view/ViewCompat;-><clinit>()V
+HSPLandroidx/core/view/accessibility/AccessibilityNodeProviderCompat;-><init>(Ljava/lang/Object;)V
+HSPLandroidx/customview/poolingcontainer/PoolingContainerListenerHolder;-><init>()V
+HSPLandroidx/emoji2/text/ConcurrencyHelpers$$ExternalSyntheticLambda0;-><init>(Ljava/lang/String;)V
+HSPLandroidx/emoji2/text/ConcurrencyHelpers$$ExternalSyntheticLambda0;->newThread(Ljava/lang/Runnable;)Ljava/lang/Thread;
+HSPLandroidx/emoji2/text/ConcurrencyHelpers$Handler28Impl;->createAsync(Landroid/os/Looper;)Landroid/os/Handler;
+HSPLandroidx/emoji2/text/DefaultGlyphChecker;-><clinit>()V
+HSPLandroidx/emoji2/text/DefaultGlyphChecker;-><init>()V
+HSPLandroidx/emoji2/text/EmojiCompat$CompatInternal19$1;-><init>(Landroidx/emoji2/text/EmojiCompat$CompatInternal19;)V
+HSPLandroidx/emoji2/text/EmojiCompat$CompatInternal19$1;->onLoaded(Landroidx/emoji2/text/MetadataRepo;)V
+HSPLandroidx/emoji2/text/EmojiCompat$CompatInternal19;-><init>(Landroidx/emoji2/text/EmojiCompat;)V
+HSPLandroidx/emoji2/text/EmojiCompat$CompatInternal19;->process(Ljava/lang/CharSequence;IZ)Ljava/lang/CharSequence;
+HSPLandroidx/emoji2/text/EmojiCompat$Config;-><init>(Landroidx/emoji2/text/EmojiCompat$MetadataRepoLoader;)V
+HSPLandroidx/emoji2/text/EmojiCompat;-><clinit>()V
+HSPLandroidx/emoji2/text/EmojiCompat;-><init>(Landroidx/emoji2/text/FontRequestEmojiCompatConfig;)V
+HSPLandroidx/emoji2/text/EmojiCompat;->get()Landroidx/emoji2/text/EmojiCompat;
+HSPLandroidx/emoji2/text/EmojiCompat;->getLoadState()I
+HSPLandroidx/emoji2/text/EmojiCompat;->load()V
+HSPLandroidx/emoji2/text/EmojiCompat;->onMetadataLoadSuccess()V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer$1;-><init>(Landroidx/emoji2/text/EmojiCompatInitializer;Landroidx/lifecycle/Lifecycle;)V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer$1;->onResume(Landroidx/lifecycle/LifecycleOwner;)V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader$$ExternalSyntheticLambda0;-><init>(Landroidx/compose/runtime/Stack;Lokhttp3/MediaType;Ljava/util/concurrent/ThreadPoolExecutor;)V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader$$ExternalSyntheticLambda0;->run()V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader$1;-><init>(Lokhttp3/MediaType;Ljava/util/concurrent/ThreadPoolExecutor;)V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader$1;->onLoaded(Landroidx/emoji2/text/MetadataRepo;)V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer$LoadEmojiCompatRunnable;->run()V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer;-><init>()V
+HSPLandroidx/emoji2/text/EmojiCompatInitializer;->create(Landroid/content/Context;)Ljava/lang/Boolean;
+HSPLandroidx/emoji2/text/EmojiCompatInitializer;->create(Landroid/content/Context;)Ljava/lang/Object;
+HSPLandroidx/emoji2/text/EmojiCompatInitializer;->dependencies()Ljava/util/List;
+HSPLandroidx/emoji2/text/EmojiProcessor$EmojiProcessAddSpanCallback;-><init>(Landroidx/emoji2/text/UnprecomputeTextOnModificationSpannable;Lkotlin/ULong$Companion;)V
+HSPLandroidx/emoji2/text/EmojiProcessor$EmojiProcessAddSpanCallback;->getResult()Ljava/lang/Object;
+HSPLandroidx/emoji2/text/EmojiProcessor$ProcessorSm;-><init>(Landroidx/emoji2/text/MetadataRepo$Node;Z[I)V
+HSPLandroidx/emoji2/text/EmojiProcessor$ProcessorSm;->shouldUseEmojiPresentationStyleForSingleCodepoint()Z
+HSPLandroidx/emoji2/text/EmojiProcessor;-><init>(Landroidx/compose/ui/node/LayoutNode;)V
+HSPLandroidx/emoji2/text/EmojiProcessor;-><init>(Landroidx/emoji2/text/MetadataRepo;Lkotlin/ULong$Companion;Landroidx/emoji2/text/DefaultGlyphChecker;Ljava/util/Set;)V
+HSPLandroidx/emoji2/text/EmojiProcessor;->process(Ljava/lang/String;IIIZLandroidx/emoji2/text/EmojiProcessor$EmojiProcessCallback;)Ljava/lang/Object;
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader$$ExternalSyntheticLambda0;-><init>(Landroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader;I)V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader$$ExternalSyntheticLambda0;->run()V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader$1;-><init>(Lkotlinx/coroutines/channels/BufferedChannel;Landroid/os/Handler;I)V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader;-><init>(Landroid/content/Context;Landroidx/core/provider/FontRequest;)V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader;->cleanUp()V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader;->load(Lokhttp3/MediaType;)V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader;->loadInternal()V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader;->retrieveFontInfo()Landroidx/core/provider/FontsContractCompat$FontInfo;
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig;-><clinit>()V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig;-><init>(Landroid/content/Context;)V
+HSPLandroidx/emoji2/text/FontRequestEmojiCompatConfig;-><init>(Landroid/content/Context;Landroidx/core/provider/FontRequest;)V
+HSPLandroidx/emoji2/text/MetadataRepo$Node;-><init>(I)V
+HSPLandroidx/emoji2/text/MetadataRepo$Node;->put(Landroidx/emoji2/text/TypefaceEmojiRasterizer;II)V
+HSPLandroidx/emoji2/text/MetadataRepo;-><init>(Landroid/graphics/Typeface;Landroidx/emoji2/text/flatbuffer/MetadataList;)V
+HSPLandroidx/emoji2/text/TypefaceEmojiRasterizer;-><clinit>()V
+HSPLandroidx/emoji2/text/TypefaceEmojiRasterizer;-><init>(Landroidx/emoji2/text/MetadataRepo;I)V
+HSPLandroidx/emoji2/text/TypefaceEmojiRasterizer;->getCodepointAt(I)I
+HSPLandroidx/emoji2/text/TypefaceEmojiRasterizer;->getCodepointsLength()I
+HSPLandroidx/emoji2/text/TypefaceEmojiRasterizer;->getMetadataItem()Landroidx/emoji2/text/flatbuffer/MetadataItem;
+HSPLandroidx/emoji2/text/flatbuffer/Table;-><init>()V
+HSPLandroidx/emoji2/text/flatbuffer/Table;->__offset(I)I
+HSPLandroidx/lifecycle/DefaultLifecycleObserver;->onResume(Landroidx/lifecycle/LifecycleOwner;)V
+HSPLandroidx/lifecycle/DefaultLifecycleObserver;->onStart(Landroidx/lifecycle/LifecycleOwner;)V
+HSPLandroidx/lifecycle/DefaultLifecycleObserverAdapter$WhenMappings;-><clinit>()V
+HSPLandroidx/lifecycle/DefaultLifecycleObserverAdapter;-><init>(Landroidx/lifecycle/DefaultLifecycleObserver;Landroidx/lifecycle/LifecycleEventObserver;)V
+HSPLandroidx/lifecycle/DefaultLifecycleObserverAdapter;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/lifecycle/EmptyActivityLifecycleCallbacks;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLandroidx/lifecycle/EmptyActivityLifecycleCallbacks;->onActivityResumed(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/EmptyActivityLifecycleCallbacks;->onActivityStarted(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/Lifecycle$Event$Companion;->upFrom(Landroidx/lifecycle/Lifecycle$State;)Landroidx/lifecycle/Lifecycle$Event;
+HSPLandroidx/lifecycle/Lifecycle$Event$WhenMappings;-><clinit>()V
+HSPLandroidx/lifecycle/Lifecycle$Event;-><clinit>()V
+HSPLandroidx/lifecycle/Lifecycle$Event;-><init>(ILjava/lang/String;)V
+HSPLandroidx/lifecycle/Lifecycle$Event;->getTargetState()Landroidx/lifecycle/Lifecycle$State;
+HSPLandroidx/lifecycle/Lifecycle$Event;->values()[Landroidx/lifecycle/Lifecycle$Event;
+HSPLandroidx/lifecycle/Lifecycle$State;-><clinit>()V
+HSPLandroidx/lifecycle/Lifecycle$State;-><init>(ILjava/lang/String;)V
+HSPLandroidx/lifecycle/Lifecycle;-><init>()V
+HSPLandroidx/lifecycle/LifecycleDestroyedException;-><init>(I)V
+HSPLandroidx/lifecycle/LifecycleDestroyedException;-><init>(II)V
+HSPLandroidx/lifecycle/LifecycleDestroyedException;->fillInStackTrace()Ljava/lang/Throwable;
+HSPLandroidx/lifecycle/LifecycleDispatcher$DispatcherActivityCallback;-><init>()V
+HSPLandroidx/lifecycle/LifecycleDispatcher$DispatcherActivityCallback;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLandroidx/lifecycle/LifecycleDispatcher;-><clinit>()V
+HSPLandroidx/lifecycle/LifecycleRegistry$ObserverWithState;-><init>(Landroidx/lifecycle/LifecycleObserver;Landroidx/lifecycle/Lifecycle$State;)V
+HSPLandroidx/lifecycle/LifecycleRegistry$ObserverWithState;->dispatchEvent(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/lifecycle/LifecycleRegistry;-><init>(Landroidx/lifecycle/LifecycleOwner;)V
+HSPLandroidx/lifecycle/LifecycleRegistry;->addObserver(Landroidx/lifecycle/LifecycleObserver;)V
+HSPLandroidx/lifecycle/LifecycleRegistry;->calculateTargetState(Landroidx/lifecycle/LifecycleObserver;)Landroidx/lifecycle/Lifecycle$State;
+HSPLandroidx/lifecycle/LifecycleRegistry;->enforceMainThreadIfNeeded(Ljava/lang/String;)V
+HSPLandroidx/lifecycle/LifecycleRegistry;->handleLifecycleEvent(Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/lifecycle/LifecycleRegistry;->moveToState(Landroidx/lifecycle/Lifecycle$State;)V
+HSPLandroidx/lifecycle/LifecycleRegistry;->removeObserver(Landroidx/lifecycle/LifecycleObserver;)V
+HSPLandroidx/lifecycle/LifecycleRegistry;->sync()V
+HSPLandroidx/lifecycle/Lifecycling;-><clinit>()V
+HSPLandroidx/lifecycle/ProcessLifecycleInitializer;-><init>()V
+HSPLandroidx/lifecycle/ProcessLifecycleInitializer;->create(Landroid/content/Context;)Ljava/lang/Object;
+HSPLandroidx/lifecycle/ProcessLifecycleInitializer;->dependencies()Ljava/util/List;
+HSPLandroidx/lifecycle/ProcessLifecycleOwner$Api29Impl;->registerActivityLifecycleCallbacks(Landroid/app/Activity;Landroid/app/Application$ActivityLifecycleCallbacks;)V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner$attach$1$onActivityPreCreated$1;-><init>(Landroidx/lifecycle/ProcessLifecycleOwner;)V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner$attach$1$onActivityPreCreated$1;->onActivityPostResumed(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner$attach$1$onActivityPreCreated$1;->onActivityPostStarted(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner$attach$1;-><init>(Landroidx/lifecycle/ProcessLifecycleOwner;)V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner$attach$1;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner$attach$1;->onActivityPreCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner$initializationListener$1;-><init>(Landroidx/lifecycle/ProcessLifecycleOwner;)V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner;-><clinit>()V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner;-><init>()V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner;->activityResumed$lifecycle_process_release()V
+HSPLandroidx/lifecycle/ProcessLifecycleOwner;->getLifecycle()Landroidx/lifecycle/LifecycleRegistry;
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;-><clinit>()V
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;-><init>()V
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityPostCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityPostResumed(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityPostStarted(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityResumed(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityStarted(Landroid/app/Activity;)V
+HSPLandroidx/lifecycle/ReportFragment;-><init>()V
+HSPLandroidx/lifecycle/ReportFragment;->dispatch(Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/lifecycle/ReportFragment;->onActivityCreated(Landroid/os/Bundle;)V
+HSPLandroidx/lifecycle/ReportFragment;->onResume()V
+HSPLandroidx/lifecycle/ReportFragment;->onStart()V
+HSPLandroidx/lifecycle/SavedStateHandleAttacher;-><init>(Landroidx/lifecycle/SavedStateHandlesProvider;)V
+HSPLandroidx/lifecycle/SavedStateHandleAttacher;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/lifecycle/SavedStateHandlesProvider;-><init>(Landroidx/savedstate/SavedStateRegistry;Landroidx/lifecycle/ViewModelStoreOwner;)V
+HSPLandroidx/lifecycle/SavedStateHandlesVM;-><init>()V
+HSPLandroidx/lifecycle/ViewModelStore;-><init>()V
+HSPLandroidx/lifecycle/viewmodel/CreationExtras$Empty;-><clinit>()V
+HSPLandroidx/lifecycle/viewmodel/CreationExtras;-><init>()V
+HSPLandroidx/lifecycle/viewmodel/MutableCreationExtras;-><init>(Landroidx/lifecycle/viewmodel/CreationExtras;)V
+HSPLandroidx/lifecycle/viewmodel/ViewModelInitializer;-><init>(Ljava/lang/Class;)V
+HSPLandroidx/metrics/performance/DelegatingFrameMetricsListener;-><init>(Ljava/util/ArrayList;)V
+HSPLandroidx/metrics/performance/DelegatingFrameMetricsListener;->onFrameMetricsAvailable(Landroid/view/Window;Landroid/view/FrameMetrics;I)V
+HSPLandroidx/metrics/performance/FrameData;-><init>(JJZLjava/util/ArrayList;)V
+HSPLandroidx/metrics/performance/FrameDataApi24;-><init>(JJJZLjava/util/ArrayList;)V
+HSPLandroidx/metrics/performance/FrameDataApi31;-><init>(JJJJZLjava/util/ArrayList;)V
+HSPLandroidx/metrics/performance/FrameDataApi31;->copy()Landroidx/metrics/performance/FrameData;
+HSPLandroidx/metrics/performance/JankStats;-><init>(Landroid/view/Window;Lcom/example/tvcomposebasedtests/JankStatsAggregator$listener$1;)V
+HSPLandroidx/metrics/performance/JankStats;->logFrameData$metrics_performance_release(Landroidx/metrics/performance/FrameData;)V
+HSPLandroidx/metrics/performance/JankStatsApi16Impl;-><init>(Landroidx/metrics/performance/JankStats;Landroid/view/View;)V
+HSPLandroidx/metrics/performance/JankStatsApi22Impl;-><init>(Landroidx/metrics/performance/JankStats;Landroid/view/View;)V
+HSPLandroidx/metrics/performance/JankStatsApi24Impl$$ExternalSyntheticLambda0;-><init>(Landroidx/metrics/performance/JankStatsApi24Impl;Landroidx/metrics/performance/JankStats;)V
+HSPLandroidx/metrics/performance/JankStatsApi24Impl$$ExternalSyntheticLambda0;->onFrameMetricsAvailable(Landroid/view/Window;Landroid/view/FrameMetrics;I)V
+HSPLandroidx/metrics/performance/JankStatsApi24Impl;-><init>(Landroidx/metrics/performance/JankStats;Landroid/view/View;Landroid/view/Window;)V
+HSPLandroidx/metrics/performance/JankStatsApi24Impl;->getOrCreateFrameMetricsListenerDelegator(Landroid/view/Window;)Landroidx/metrics/performance/DelegatingFrameMetricsListener;
+HSPLandroidx/metrics/performance/JankStatsApi24Impl;->setupFrameTimer(Z)V
+HSPLandroidx/metrics/performance/JankStatsApi26Impl;-><init>(Landroidx/metrics/performance/JankStats;Landroid/view/View;Landroid/view/Window;)V
+HSPLandroidx/metrics/performance/JankStatsApi26Impl;->getFrameStartTime$metrics_performance_release(Landroid/view/FrameMetrics;)J
+HSPLandroidx/metrics/performance/JankStatsApi31Impl;-><init>(Landroidx/metrics/performance/JankStats;Landroid/view/View;Landroid/view/Window;)V
+HSPLandroidx/metrics/performance/JankStatsApi31Impl;->getExpectedFrameDuration(Landroid/view/FrameMetrics;)J
+HSPLandroidx/metrics/performance/JankStatsApi31Impl;->getFrameData$metrics_performance_release(JJLandroid/view/FrameMetrics;)Landroidx/metrics/performance/FrameDataApi24;
+HSPLandroidx/metrics/performance/PerformanceMetricsState;-><init>()V
+HSPLandroidx/metrics/performance/PerformanceMetricsState;->addFrameState(JJLjava/util/ArrayList;Ljava/util/ArrayList;)V
+HSPLandroidx/metrics/performance/PerformanceMetricsState;->cleanupSingleFrameStates$metrics_performance_release()V
+HSPLandroidx/metrics/performance/PerformanceMetricsState;->getIntervalStates$metrics_performance_release(JJLjava/util/ArrayList;)V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda0;-><init>(Ljava/lang/Object;ILjava/lang/Object;)V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda0;->run()V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda1;-><init>(Landroid/content/Context;I)V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$Choreographer16Impl;->postFrameCallback(Ljava/lang/Runnable;)V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer$Handler28Impl;->createAsync(Landroid/os/Looper;)Landroid/os/Handler;
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer;-><init>()V
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer;->create(Landroid/content/Context;)Ljava/lang/Object;
+HSPLandroidx/profileinstaller/ProfileInstallerInitializer;->dependencies()Ljava/util/List;
+HSPLandroidx/savedstate/Recreator;-><init>(Landroidx/savedstate/SavedStateRegistryOwner;)V
+HSPLandroidx/savedstate/Recreator;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/savedstate/SavedStateRegistry$$ExternalSyntheticLambda0;-><init>(Landroidx/savedstate/SavedStateRegistry;)V
+HSPLandroidx/savedstate/SavedStateRegistry$$ExternalSyntheticLambda0;->onStateChanged(Landroidx/lifecycle/LifecycleOwner;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLandroidx/savedstate/SavedStateRegistry;-><init>()V
+HSPLandroidx/savedstate/SavedStateRegistry;->consumeRestoredStateForKey(Ljava/lang/String;)Landroid/os/Bundle;
+HSPLandroidx/savedstate/SavedStateRegistry;->registerSavedStateProvider(Ljava/lang/String;Landroidx/savedstate/SavedStateRegistry$SavedStateProvider;)V
+HSPLandroidx/savedstate/SavedStateRegistryController;-><init>(Landroidx/savedstate/SavedStateRegistryOwner;)V
+HSPLandroidx/savedstate/SavedStateRegistryController;->performAttach()V
+HSPLandroidx/startup/AppInitializer;-><clinit>()V
+HSPLandroidx/startup/AppInitializer;-><init>(Landroid/content/Context;)V
+HSPLandroidx/startup/AppInitializer;->discoverAndInitialize(Landroid/os/Bundle;)V
+HSPLandroidx/startup/AppInitializer;->doInitialize(Ljava/lang/Class;Ljava/util/HashSet;)Ljava/lang/Object;
+HSPLandroidx/startup/AppInitializer;->getInstance(Landroid/content/Context;)Landroidx/startup/AppInitializer;
+HSPLandroidx/startup/InitializationProvider;-><init>()V
+HSPLandroidx/startup/InitializationProvider;->onCreate()Z
+HSPLandroidx/tracing/Trace$$ExternalSyntheticApiModelOutline0;->m()Z
+HSPLandroidx/tracing/Trace$$ExternalSyntheticApiModelOutline0;->m(Landroid/app/Activity;Landroidx/lifecycle/ReportFragment$LifecycleCallbacks;)V
+HSPLandroidx/tv/foundation/PivotOffsets;-><init>()V
+HSPLandroidx/tv/foundation/TvBringIntoViewSpec;-><init>(Landroidx/tv/foundation/PivotOffsets;Z)V
+HSPLandroidx/tv/foundation/TvBringIntoViewSpec;->calculateScrollDistance(FFF)F
+HSPLandroidx/tv/foundation/TvBringIntoViewSpec;->getScrollAnimationSpec()Landroidx/compose/animation/core/AnimationSpec;
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridItemPlacementAnimator;-><init>(I)V
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridItemPlacementAnimator;->reset()V
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridKt$rememberLazyGridMeasurePolicy$1$1$3;-><init>(Landroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;JIII)V
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridKt$rememberLazyGridMeasurePolicy$1$1$3;->invoke(IILkotlin/jvm/functions/Function1;)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridKt$rememberLazyGridMeasurePolicy$1$1$3;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridScrollPosition;-><init>(III)V
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridScrollPosition;->getIndex()I
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridScrollPosition;->setIndex(I)V
+HSPLandroidx/tv/foundation/lazy/grid/LazyGridScrollPosition;->update(II)V
+HSPLandroidx/tv/foundation/lazy/grid/TvLazyGridState$remeasurementModifier$1;-><init>(Landroidx/compose/foundation/gestures/ScrollableState;I)V
+HSPLandroidx/tv/foundation/lazy/layout/AwaitFirstLayoutModifier$waitForFirstLayout$1;-><init>(Landroidx/tv/foundation/lazy/layout/AwaitFirstLayoutModifier;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/tv/foundation/lazy/layout/AwaitFirstLayoutModifier;->waitForFirstLayout(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/layout/LazyLayoutNearestRangeState;-><clinit>()V
+HSPLandroidx/tv/foundation/lazy/layout/LazyLayoutNearestRangeState;-><init>(III)V
+HSPLandroidx/tv/foundation/lazy/layout/LazyLayoutNearestRangeState;->getValue()Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/layout/LazyLayoutNearestRangeState;->update(I)V
+HSPLandroidx/tv/foundation/lazy/layout/LazyLayoutSemanticsKt$LazyLayoutSemanticState$1;-><init>(Landroidx/tv/foundation/lazy/list/TvLazyListState;Z)V
+HSPLandroidx/tv/foundation/lazy/layout/LazyLayoutSemanticsKt$LazyLayoutSemanticState$1;->collectionInfo()Landroidx/compose/ui/semantics/CollectionInfo;
+HSPLandroidx/tv/foundation/lazy/layout/LazyLayoutSemanticsKt$lazyLayoutSemantics$1$1;-><init>(Landroidx/tv/material3/TabKt$Tab$3$1;ZLandroidx/compose/ui/semantics/ScrollAxisRange;Landroidx/compose/foundation/ScrollKt$scroll$2$semantics$1$1;Landroidx/compose/foundation/layout/OffsetNode$measure$1;Landroidx/compose/ui/semantics/CollectionInfo;)V
+HSPLandroidx/tv/foundation/lazy/layout/NearestRangeKeyIndexMap$2$1;-><init>(IILjava/util/HashMap;Landroidx/tv/foundation/lazy/layout/NearestRangeKeyIndexMap;)V
+HSPLandroidx/tv/foundation/lazy/layout/NearestRangeKeyIndexMap$2$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/layout/NearestRangeKeyIndexMap;-><init>(Lkotlin/ranges/IntRange;Landroidx/tv/material3/TabKt;)V
+HSPLandroidx/tv/foundation/lazy/layout/NearestRangeKeyIndexMap;->getKey(I)Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/list/EmptyLazyListLayoutInfo;-><clinit>()V
+HSPLandroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal;-><clinit>()V
+HSPLandroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal;-><init>(Landroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsState;Landroidx/compose/runtime/Stack;ZLandroidx/compose/ui/unit/LayoutDirection;Landroidx/compose/foundation/gestures/Orientation;)V
+HSPLandroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal;->getKey()Landroidx/compose/ui/modifier/ProvidableModifierLocal;
+HSPLandroidx/tv/foundation/lazy/list/LazyListBeyondBoundsState;-><init>(Landroidx/tv/foundation/lazy/list/TvLazyListState;I)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListItemProviderImpl;-><init>(Landroidx/tv/foundation/lazy/list/TvLazyListState;Landroidx/tv/foundation/lazy/list/TvLazyListIntervalContent;Landroidx/tv/foundation/lazy/list/TvLazyListItemScopeImpl;Landroidx/tv/foundation/lazy/layout/NearestRangeKeyIndexMap;)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListItemProviderImpl;->Item(ILjava/lang/Object;Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListItemProviderImpl;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/tv/foundation/lazy/list/LazyListItemProviderImpl;->getContentType(I)Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/list/LazyListItemProviderImpl;->getItemCount()I
+HSPLandroidx/tv/foundation/lazy/list/LazyListItemProviderImpl;->getKey(I)Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/list/LazyListKt$rememberLazyListMeasurePolicy$1$1$measuredItemProvider$1;-><init>(JZLandroidx/tv/foundation/lazy/list/LazyListItemProviderImpl;Landroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;IILandroidx/compose/ui/Alignment$Horizontal;Landroidx/compose/ui/Alignment$Vertical;ZIIJ)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListKt$rememberLazyListMeasurePolicy$1$1$measuredItemProvider$1;->getAndMeasure(I)Landroidx/tv/foundation/lazy/list/LazyListMeasuredItem;
+HSPLandroidx/tv/foundation/lazy/list/LazyListKt$rememberLazyListMeasurePolicy$1$1;-><init>(Landroidx/tv/foundation/lazy/list/TvLazyListState;ZLandroidx/compose/foundation/layout/PaddingValuesImpl;ZLkotlin/reflect/KProperty0;Landroidx/compose/foundation/layout/Arrangement$Vertical;Landroidx/compose/foundation/layout/Arrangement$Horizontal;ILandroidx/compose/ui/Alignment$Horizontal;Landroidx/compose/ui/Alignment$Vertical;)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListKt$rememberLazyListMeasurePolicy$1$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasureResult;-><init>(Landroidx/tv/foundation/lazy/list/LazyListMeasuredItem;IZFLandroidx/compose/ui/layout/MeasureResult;FLjava/util/List;II)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasureResult;->getAlignmentLines()Ljava/util/Map;
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasureResult;->getHeight()I
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasureResult;->getTotalItemsCount()I
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasureResult;->getVisibleItemsInfo()Ljava/util/List;
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasureResult;->getWidth()I
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasureResult;->placeChildren()V
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasuredItem;-><init>(ILjava/util/List;ZLandroidx/compose/ui/Alignment$Horizontal;Landroidx/compose/ui/Alignment$Vertical;Landroidx/compose/ui/unit/LayoutDirection;ZIIIJLjava/lang/Object;Ljava/lang/Object;)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasuredItem;->getParentData(I)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasuredItem;->place(Landroidx/compose/ui/layout/Placeable$PlacementScope;)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListMeasuredItem;->position(III)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListStateKt$rememberTvLazyListState$1$1;-><init>(III)V
+HSPLandroidx/tv/foundation/lazy/list/LazyListStateKt$rememberTvLazyListState$1$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/list/LazyListStateKt;-><clinit>()V
+HSPLandroidx/tv/foundation/lazy/list/LazyListStateKt;->rememberTvLazyListState(Landroidx/compose/runtime/Composer;)Landroidx/tv/foundation/lazy/list/TvLazyListState;
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListInterval;-><init>(Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListInterval;->getKey()Lkotlin/jvm/functions/Function1;
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListInterval;->getType()Lkotlin/jvm/functions/Function1;
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListIntervalContent;-><init>(Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListIntervalContent;->getIntervals()Landroidx/compose/foundation/lazy/layout/MutableIntervalList;
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListItemScopeImpl;-><init>()V
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListScope;->items$default(Landroidx/tv/foundation/lazy/list/TvLazyListScope;ILandroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListState$scroll$1;-><init>(Landroidx/tv/foundation/lazy/list/TvLazyListState;Lkotlin/coroutines/Continuation;)V
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListState$scroll$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListState;-><clinit>()V
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListState;-><init>(II)V
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListState;->applyMeasureResult$tv_foundation_release(Landroidx/tv/foundation/lazy/list/LazyListMeasureResult;Z)V
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListState;->getCanScrollBackward()Z
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListState;->getCanScrollForward()Z
+HSPLandroidx/tv/foundation/lazy/list/TvLazyListState;->scroll(Landroidx/compose/foundation/MutatePriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/Border;-><clinit>()V
+HSPLandroidx/tv/material3/Border;-><init>(Landroidx/compose/foundation/BorderStroke;FLandroidx/compose/ui/graphics/Shape;)V
+HSPLandroidx/tv/material3/Border;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/tv/material3/ColorScheme;-><init>(JJJJJJJJJJJJJJJJJJJJJJJJJJJJJ)V
+HSPLandroidx/tv/material3/ColorScheme;->getInverseSurface-0d7_KjU()J
+HSPLandroidx/tv/material3/ColorScheme;->getOnSurface-0d7_KjU()J
+HSPLandroidx/tv/material3/ColorScheme;->getSurface-0d7_KjU()J
+HSPLandroidx/tv/material3/ColorSchemeKt$LocalColorScheme$1;-><clinit>()V
+HSPLandroidx/tv/material3/ColorSchemeKt$LocalColorScheme$1;-><init>(I)V
+HSPLandroidx/tv/material3/ColorSchemeKt$LocalColorScheme$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/tv/material3/ColorSchemeKt;-><clinit>()V
+HSPLandroidx/tv/material3/ColorSchemeKt;->contentColorFor-ek8zF_U(JLandroidx/compose/runtime/Composer;)J
+HSPLandroidx/tv/material3/ContentColorKt;-><clinit>()V
+HSPLandroidx/tv/material3/Glow;-><clinit>()V
+HSPLandroidx/tv/material3/Glow;-><init>(JF)V
+HSPLandroidx/tv/material3/Glow;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/tv/material3/GlowIndication;-><init>(JLandroidx/compose/ui/graphics/Shape;FFF)V
+HSPLandroidx/tv/material3/GlowIndication;->rememberUpdatedInstance(Landroidx/compose/foundation/interaction/InteractionSource;Landroidx/compose/runtime/Composer;)Landroidx/compose/foundation/IndicationInstance;
+HSPLandroidx/tv/material3/GlowIndicationInstance;-><init>(JLandroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/unit/Density;FFF)V
+HSPLandroidx/tv/material3/GlowIndicationInstance;->drawIndication(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/tv/material3/ScaleIndication;-><init>(F)V
+HSPLandroidx/tv/material3/ScaleIndicationInstance;-><init>(F)V
+HSPLandroidx/tv/material3/ScaleIndicationInstance;->drawIndication(Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;)V
+HSPLandroidx/tv/material3/ScaleIndicationTokens;-><clinit>()V
+HSPLandroidx/tv/material3/ShapesKt$LocalShapes$1;-><clinit>()V
+HSPLandroidx/tv/material3/ShapesKt$LocalShapes$1;-><init>(I)V
+HSPLandroidx/tv/material3/ShapesKt$LocalShapes$1;->invoke()Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$Surface$4;-><init>(ZLkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZLkotlin/jvm/functions/Function0;FLandroidx/tv/material3/ToggleableSurfaceShape;Landroidx/tv/material3/ToggleableSurfaceColors;Landroidx/tv/material3/ToggleableSurfaceScale;Landroidx/tv/material3/ToggleableSurfaceBorder;Landroidx/tv/material3/ToggleableSurfaceGlow;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Lkotlin/jvm/functions/Function3;III)V
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$2$2$1;-><init>(ILjava/lang/Object;)V
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$2$2$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$2$4$1;-><init>(Landroidx/compose/ui/graphics/Shape;JI)V
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$2$4$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$2$5$1;-><init>(FLandroidx/compose/ui/graphics/Shape;)V
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$2$5$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$2;-><init>(JILandroidx/compose/ui/Modifier;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;FLandroidx/tv/material3/Glow;Landroidx/compose/ui/graphics/Shape;Landroidx/tv/material3/Border;FLandroidx/compose/runtime/MutableState;ZLkotlin/jvm/functions/Function3;I)V
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$3;-><init>(Landroidx/compose/ui/Modifier;ZZLandroidx/compose/ui/graphics/Shape;JJFLandroidx/tv/material3/Border;Landroidx/tv/material3/Glow;FLandroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Lkotlin/jvm/functions/Function3;III)V
+HSPLandroidx/tv/material3/SurfaceKt$SurfaceImpl$3;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$handleDPadEnter$2$1;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;I)V
+HSPLandroidx/tv/material3/SurfaceKt$handleDPadEnter$2$1;->invoke(Landroidx/compose/animation/core/AnimationScope;)V
+HSPLandroidx/tv/material3/SurfaceKt$handleDPadEnter$2$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$handleDPadEnter$2$2;-><init>(Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;ZLandroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Landroidx/compose/foundation/interaction/PressInteraction$Press;Landroidx/compose/runtime/MutableState;)V
+HSPLandroidx/tv/material3/SurfaceKt$handleDPadEnter$2$2;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$handleDPadEnter$2;-><init>(Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;ZZ)V
+HSPLandroidx/tv/material3/SurfaceKt$handleDPadEnter$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$tvToggleable$1$1;-><init>(Landroidx/compose/ui/platform/AndroidComposeView;Z)V
+HSPLandroidx/tv/material3/SurfaceKt$tvToggleable$1$2;-><init>(Lkotlin/jvm/functions/Function0;I)V
+HSPLandroidx/tv/material3/SurfaceKt$tvToggleable$1$2;->invoke()Ljava/lang/Object;
+HSPLandroidx/tv/material3/SurfaceKt$tvToggleable$1;-><init>(ZLkotlin/jvm/functions/Function1;ZLkotlin/jvm/functions/Function0;)V
+HSPLandroidx/tv/material3/SurfaceKt;-><clinit>()V
+HSPLandroidx/tv/material3/SurfaceKt;->Surface-xYaah8o(ZLkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZLkotlin/jvm/functions/Function0;FLandroidx/tv/material3/ToggleableSurfaceShape;Landroidx/tv/material3/ToggleableSurfaceColors;Landroidx/tv/material3/ToggleableSurfaceScale;Landroidx/tv/material3/ToggleableSurfaceBorder;Landroidx/tv/material3/ToggleableSurfaceGlow;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V
+HSPLandroidx/tv/material3/SurfaceKt;->Surface_xYaah8o$lambda$4(Landroidx/compose/runtime/MutableState;)Z
+HSPLandroidx/tv/material3/SurfaceKt;->Surface_xYaah8o$lambda$5(Landroidx/compose/runtime/MutableState;)Z
+HSPLandroidx/tv/material3/SurfaceKt;->access$surfaceColorAtElevation-CLU3JFs(JFLandroidx/compose/runtime/Composer;)J
+HSPLandroidx/tv/material3/TabColors;-><init>(JJJJJJJJ)V
+HSPLandroidx/tv/material3/TabKt$Tab$1;-><clinit>()V
+HSPLandroidx/tv/material3/TabKt$Tab$1;-><init>()V
+HSPLandroidx/tv/material3/TabKt$Tab$3$1;-><init>(Lkotlin/jvm/functions/Function0;I)V
+HSPLandroidx/tv/material3/TabKt$Tab$3$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabKt$Tab$4$1;-><init>(IZ)V
+HSPLandroidx/tv/material3/TabKt$Tab$4$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabKt$Tab$6;-><init>(Lkotlin/jvm/functions/Function3;I)V
+HSPLandroidx/tv/material3/TabKt$Tab$6;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabKt$Tab$7;-><init>(Landroidx/tv/material3/TabRowScopeImpl;ZLkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function0;ZLandroidx/tv/material3/TabColors;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Lkotlin/jvm/functions/Function3;II)V
+HSPLandroidx/tv/material3/TabKt;-><clinit>()V
+HSPLandroidx/tv/material3/TabKt;->BasicText-BpD7jsM(Ljava/lang/String;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/text/TextStyle;Lkotlin/jvm/functions/Function1;IZILandroidx/compose/runtime/Composer;II)V
+HSPLandroidx/tv/material3/TabKt;->BasicText-VhcvRP8(Ljava/lang/String;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/text/TextStyle;Lkotlin/jvm/functions/Function1;IZIILandroidx/compose/runtime/Composer;II)V
+HSPLandroidx/tv/material3/TabKt;->DisposableEffect(Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;)V
+HSPLandroidx/tv/material3/TabKt;->LaunchedEffect(Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;)V
+HSPLandroidx/tv/material3/TabKt;->LaunchedEffect(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;)V
+HSPLandroidx/tv/material3/TabKt;->LazyLayout(Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V
+HSPLandroidx/tv/material3/TabKt;->LazyLayoutPrefetcher(Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState;Landroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;Landroidx/compose/ui/layout/SubcomposeLayoutState;Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/tv/material3/TabKt;->SideEffect(Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;)V
+HSPLandroidx/tv/material3/TabKt;->Tab(Landroidx/tv/material3/TabRowScopeImpl;ZLkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function0;ZLandroidx/tv/material3/TabColors;Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
+HSPLandroidx/tv/material3/TabKt;->TabRow-pAZo6Ak(ILandroidx/compose/ui/Modifier;JJLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
+HSPLandroidx/tv/material3/TabKt;->access$binarySearch(ILandroidx/compose/runtime/collection/MutableVector;)I
+HSPLandroidx/tv/material3/TabKt;->animate(Landroidx/compose/animation/core/AnimationState;Landroidx/compose/animation/core/Animation;JLkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabKt;->callWithFrameNanos(Landroidx/compose/animation/core/Animation;Lkotlin/jvm/functions/Function1;Landroidx/compose/animation/core/SuspendAnimationKt$animate$4;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabKt;->collectIsFocusedAsState(Landroidx/compose/foundation/interaction/InteractionSource;Landroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/MutableState;
+HSPLandroidx/tv/material3/TabKt;->complexSqrt(D)Landroidx/compose/animation/core/ComplexDouble;
+HSPLandroidx/tv/material3/TabKt;->copy(Landroidx/compose/animation/core/AnimationVector;)Landroidx/compose/animation/core/AnimationVector;
+HSPLandroidx/tv/material3/TabKt;->createCompositionCoroutineScope(Landroidx/compose/runtime/Composer;)Lkotlinx/coroutines/internal/ContextScope;
+HSPLandroidx/tv/material3/TabKt;->derivedStateObservers()Landroidx/compose/runtime/collection/MutableVector;
+HSPLandroidx/tv/material3/TabKt;->doAnimationFrameWithScale(Landroidx/compose/animation/core/AnimationScope;JFLandroidx/compose/animation/core/Animation;Landroidx/compose/animation/core/AnimationState;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/tv/material3/TabKt;->finalConstraints-tfFHcEY(JZIF)J
+HSPLandroidx/tv/material3/TabKt;->getContentType(I)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabKt;->getDurationScale(Lkotlin/coroutines/CoroutineContext;)F
+HSPLandroidx/tv/material3/TabKt;->getPoolingContainerListenerHolder(Landroid/view/View;)Landroidx/customview/poolingcontainer/PoolingContainerListenerHolder;
+HSPLandroidx/tv/material3/TabKt;->mutableStateOf$default(Ljava/lang/Object;)Landroidx/compose/runtime/ParcelableSnapshotMutableState;
+HSPLandroidx/tv/material3/TabKt;->read(Landroidx/compose/runtime/PersistentCompositionLocalMap;Landroidx/compose/runtime/ProvidableCompositionLocal;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabKt;->rememberSaveable([Ljava/lang/Object;Landroidx/compose/runtime/saveable/SaverKt$Saver$1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabKt;->rememberUpdatedState(Ljava/lang/Object;Landroidx/compose/runtime/Composer;)Landroidx/compose/runtime/MutableState;
+HSPLandroidx/tv/material3/TabKt;->resumeCancellableWith(Lkotlin/coroutines/Continuation;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V
+HSPLandroidx/tv/material3/TabKt;->spring$default(FLjava/lang/Comparable;I)Landroidx/compose/animation/core/SpringSpec;
+HSPLandroidx/tv/material3/TabKt;->tween$default(ILandroidx/compose/animation/core/Easing;)Landroidx/compose/animation/core/TweenSpec;
+HSPLandroidx/tv/material3/TabKt;->updateCompositionMap([Landroidx/compose/runtime/ProvidedValue;Landroidx/compose/runtime/PersistentCompositionLocalMap;Landroidx/compose/runtime/PersistentCompositionLocalMap;)Landroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;
+HSPLandroidx/tv/material3/TabKt;->updateState(Landroidx/compose/animation/core/AnimationScope;Landroidx/compose/animation/core/AnimationState;)V
+HSPLandroidx/tv/material3/TabKt;->withFrameNanos(Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowDefaults$PillIndicator$1;-><init>(Landroidx/tv/material3/TabRowDefaults;Landroidx/compose/ui/unit/DpRect;ZLandroidx/compose/ui/Modifier;JJII)V
+HSPLandroidx/tv/material3/TabRowDefaults$PillIndicator$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowDefaults;-><clinit>()V
+HSPLandroidx/tv/material3/TabRowDefaults;->PillIndicator-jA1GFJw(Landroidx/compose/ui/unit/DpRect;ZLandroidx/compose/ui/Modifier;JJLandroidx/compose/runtime/Composer;II)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$1;-><init>(I)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$1$1;-><init>(Landroidx/compose/runtime/MutableState;I)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$1$1;->invoke(Landroidx/compose/ui/focus/FocusState;)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$1$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$1$2;-><init>(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;II)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$1$2;-><init>(Lkotlin/jvm/functions/Function4;Ljava/util/ArrayList;ILandroidx/compose/runtime/MutableState;)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$1$2;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$1$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$1;-><init>(Ljava/util/ArrayList;Landroidx/compose/ui/layout/SubcomposeMeasureScope;Ljava/util/ArrayList;ILkotlin/jvm/functions/Function4;ILandroidx/compose/runtime/MutableState;II)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$separators$1;-><init>(IILkotlin/jvm/functions/Function1;)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$separators$1;-><init>(ILkotlin/jvm/functions/Function2;I)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$separators$1;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1$separators$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1;-><init>(Landroidx/compose/runtime/MutableState;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function4;)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2$2$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2;-><init>(Landroidx/compose/ui/Modifier;JLandroidx/compose/foundation/ScrollState;Landroidx/compose/runtime/MutableState;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function4;I)V
+HSPLandroidx/tv/material3/TabRowKt$TabRow$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TabRowKt$TabRow$3;-><init>(ILandroidx/compose/ui/Modifier;JJLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function3;II)V
+HSPLandroidx/tv/material3/TabRowScopeImpl;-><init>(Z)V
+HSPLandroidx/tv/material3/TabRowSlots;-><clinit>()V
+HSPLandroidx/tv/material3/TabRowSlots;-><init>(ILjava/lang/String;)V
+HSPLandroidx/tv/material3/TextKt$Text$1;-><clinit>()V
+HSPLandroidx/tv/material3/TextKt$Text$1;-><init>()V
+HSPLandroidx/tv/material3/TextKt$Text$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/TextKt$Text$2;-><init>(Ljava/lang/String;Landroidx/compose/ui/Modifier;JJLandroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontFamily;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/text/style/TextAlign;JIZILkotlin/jvm/functions/Function1;Landroidx/compose/ui/text/TextStyle;IIII)V
+HSPLandroidx/tv/material3/TextKt;-><clinit>()V
+HSPLandroidx/tv/material3/TextKt;->Text-fLXpl1I(Ljava/lang/String;Landroidx/compose/ui/Modifier;JJLandroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontFamily;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/text/style/TextAlign;JIZILkotlin/jvm/functions/Function1;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/runtime/Composer;III)V
+HSPLandroidx/tv/material3/ToggleableSurfaceBorder;-><init>(Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;Landroidx/tv/material3/Border;)V
+HSPLandroidx/tv/material3/ToggleableSurfaceColors;-><init>(JJJJJJJJJJJJJJ)V
+HSPLandroidx/tv/material3/ToggleableSurfaceColors;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/tv/material3/ToggleableSurfaceGlow;-><init>(Landroidx/tv/material3/Glow;Landroidx/tv/material3/Glow;Landroidx/tv/material3/Glow;Landroidx/tv/material3/Glow;Landroidx/tv/material3/Glow;Landroidx/tv/material3/Glow;)V
+HSPLandroidx/tv/material3/ToggleableSurfaceScale;-><clinit>()V
+HSPLandroidx/tv/material3/ToggleableSurfaceScale;-><init>(FFFFFFFFFF)V
+HSPLandroidx/tv/material3/ToggleableSurfaceShape;-><init>(Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;)V
+HSPLandroidx/tv/material3/ToggleableSurfaceShape;->equals(Ljava/lang/Object;)Z
+HSPLandroidx/tv/material3/tokens/ColorLightTokens;-><clinit>()V
+HSPLandroidx/tv/material3/tokens/Elevation;-><clinit>()V
+HSPLandroidx/tv/material3/tokens/PaletteTokens;-><clinit>()V
+HSPLandroidx/tv/material3/tokens/ShapeTokens$BorderDefaultShape$1;-><clinit>()V
+HSPLandroidx/tv/material3/tokens/ShapeTokens$BorderDefaultShape$1;-><init>(I)V
+HSPLandroidx/tv/material3/tokens/ShapeTokens$BorderDefaultShape$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLandroidx/tv/material3/tokens/ShapeTokens$BorderDefaultShape$1;->invoke(Ljava/util/List;II)Ljava/lang/Integer;
+HSPLandroidx/tv/material3/tokens/ShapeTokens$BorderDefaultShape$1;->invoke-3p2s80s(Landroidx/compose/ui/layout/MeasureScope;Landroidx/compose/ui/layout/Measurable;J)Landroidx/compose/ui/layout/MeasureResult;
+HSPLandroidx/tv/material3/tokens/TypographyTokensKt;-><clinit>()V
+HSPLcom/example/tvcomposebasedtests/ComposableSingletons$MainActivityKt;-><clinit>()V
+HSPLcom/example/tvcomposebasedtests/ComposableSingletons$UtilsKt$lambda-1$1;-><clinit>()V
+HSPLcom/example/tvcomposebasedtests/ComposableSingletons$UtilsKt$lambda-1$1;-><init>(I)V
+HSPLcom/example/tvcomposebasedtests/ComposableSingletons$UtilsKt$lambda-1$1;->invoke(Landroidx/tv/foundation/lazy/list/TvLazyListItemScopeImpl;ILandroidx/compose/runtime/Composer;I)V
+HSPLcom/example/tvcomposebasedtests/ComposableSingletons$UtilsKt$lambda-1$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLcom/example/tvcomposebasedtests/Config;-><init>(Landroid/content/Context;Landroidx/activity/ComponentActivity;II)V
+HSPLcom/example/tvcomposebasedtests/Config;->toString()Ljava/lang/String;
+HSPLcom/example/tvcomposebasedtests/JankStatsAggregator$listener$1;-><init>(Lcom/example/tvcomposebasedtests/JankStatsAggregator;)V
+HSPLcom/example/tvcomposebasedtests/JankStatsAggregator;-><init>(Landroid/view/Window;Lcom/example/tvcomposebasedtests/MainActivity$jankReportListener$1;)V
+HSPLcom/example/tvcomposebasedtests/MainActivity$jankReportListener$1;-><init>(Lcom/example/tvcomposebasedtests/MainActivity;)V
+HSPLcom/example/tvcomposebasedtests/MainActivity$startFrameMetrics$listener$1;-><init>(Lcom/example/tvcomposebasedtests/MainActivity;)V
+HSPLcom/example/tvcomposebasedtests/MainActivity$startFrameMetrics$listener$1;->onFrameMetricsAvailable(Landroid/view/Window;Landroid/view/FrameMetrics;I)V
+HSPLcom/example/tvcomposebasedtests/MainActivity;-><init>()V
+HSPLcom/example/tvcomposebasedtests/MainActivity;->onCreate(Landroid/os/Bundle;)V
+HSPLcom/example/tvcomposebasedtests/MainActivity;->onResume()V
+HSPLcom/example/tvcomposebasedtests/UtilsKt$AddJankMetrics$1$2;-><init>(ILjava/lang/Object;)V
+HSPLcom/example/tvcomposebasedtests/UtilsKt$AddJankMetrics$1$2;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLcom/example/tvcomposebasedtests/UtilsKt$ScrollingRow$1$1$1;-><init>(II)V
+HSPLcom/example/tvcomposebasedtests/UtilsKt$ScrollingRow$1$1$1;->invoke(Landroidx/tv/foundation/lazy/list/TvLazyListScope;)V
+HSPLcom/example/tvcomposebasedtests/UtilsKt$ScrollingRow$1$1$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLcom/example/tvcomposebasedtests/UtilsKt$ScrollingRow$2;-><init>(III)V
+HSPLcom/example/tvcomposebasedtests/UtilsKt$ScrollingRow$2;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLcom/example/tvcomposebasedtests/UtilsKt$ScrollingRow$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLcom/example/tvcomposebasedtests/UtilsKt;-><clinit>()V
+HSPLcom/example/tvcomposebasedtests/tvComponents/AppKt$App$1;-><init>(ILjava/lang/Object;)V
+HSPLcom/example/tvcomposebasedtests/tvComponents/AppKt$App$1;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLcom/example/tvcomposebasedtests/tvComponents/AppKt$App$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLcom/example/tvcomposebasedtests/tvComponents/AppKt$App$1;->invoke-5SAbXVA(JLandroidx/compose/ui/unit/LayoutDirection;)J
+HSPLcom/example/tvcomposebasedtests/tvComponents/ComposableSingletons$LazyContainersKt;-><clinit>()V
+HSPLcom/example/tvcomposebasedtests/tvComponents/ComposableSingletons$TopNavigationKt;-><clinit>()V
+HSPLcom/example/tvcomposebasedtests/tvComponents/Navigation;-><clinit>()V
+HSPLcom/example/tvcomposebasedtests/tvComponents/Navigation;-><init>(Ljava/lang/String;ILjava/lang/String;Landroidx/compose/runtime/internal/ComposableLambdaImpl;)V
+HSPLcom/example/tvcomposebasedtests/tvComponents/Navigation;->values()[Lcom/example/tvcomposebasedtests/tvComponents/Navigation;
+HSPLcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$PillIndicatorTabRow$1$1$1$1;-><init>(ILkotlin/jvm/functions/Function1;)V
+HSPLcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$PillIndicatorTabRow$1$1$1$1;->invoke()Ljava/lang/Object;
+HSPLcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$PillIndicatorTabRow$1;-><init>(IILjava/util/List;Lkotlin/jvm/functions/Function1;)V
+HSPLcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$PillIndicatorTabRow$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$TopNavigation$3$1;-><init>(Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/MutableState;Lkotlin/coroutines/Continuation;)V
+HSPLcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$TopNavigation$3$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$TopNavigation$3$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLcom/google/gson/internal/ConstructorConstructor;-><init>()V
+HSPLcom/google/gson/internal/ConstructorConstructor;->access$commitTransaction(Lcom/google/gson/internal/ConstructorConstructor;)V
+HSPLcom/google/gson/internal/LinkedTreeMap$1;-><init>(I)V
+HSPLcom/google/gson/internal/LinkedTreeMap$1;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
+HSPLkotlin/Pair;-><init>(Ljava/lang/Object;Ljava/lang/Object;)V
+HSPLkotlin/Result$Failure;-><init>(Ljava/lang/Throwable;)V
+HSPLkotlin/Result;->exceptionOrNull-impl(Ljava/lang/Object;)Ljava/lang/Throwable;
+HSPLkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;->m$1()Ljava/util/Iterator;
+HSPLkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;->m(III)I
+HSPLkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;->m(ILjava/lang/String;)V
+HSPLkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;->m(Ljava/lang/Object;)V
+HSPLkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;->stringValueOf$1(I)Ljava/lang/String;
+HSPLkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;->stringValueOf$2(I)Ljava/lang/String;
+HSPLkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;->valueOf$1(Ljava/lang/String;)I
+HSPLkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;->valueOf(Ljava/lang/String;)I
+HSPLkotlin/ResultKt;-><clinit>()V
+HSPLkotlin/ResultKt;-><init>(Landroidx/metrics/performance/JankStats;)V
+HSPLkotlin/ResultKt;->App(Landroidx/compose/runtime/Composer;I)V
+HSPLkotlin/ResultKt;->Constraints$default(III)J
+HSPLkotlin/ResultKt;->Constraints(IIII)J
+HSPLkotlin/ResultKt;->Density(Landroid/content/Context;)Landroidx/compose/ui/unit/DensityImpl;
+HSPLkotlin/ResultKt;->DpOffset-YgX7TsA(FF)J
+HSPLkotlin/ResultKt;->IntOffset(II)J
+HSPLkotlin/ResultKt;->IntSize(II)J
+HSPLkotlin/ResultKt;->MaterialTheme(Landroidx/compose/material3/ColorScheme;Landroidx/compose/material3/Shapes;Landroidx/compose/material3/Typography;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V
+HSPLkotlin/ResultKt;->SampleCardItem(ILandroidx/compose/runtime/Composer;I)V
+HSPLkotlin/ResultKt;->SampleTvLazyRow(ILandroidx/compose/runtime/Composer;I)V
+HSPLkotlin/ResultKt;->TvLazyRow(Landroidx/compose/ui/Modifier;Landroidx/tv/foundation/lazy/list/TvLazyListState;Landroidx/compose/foundation/layout/PaddingValuesImpl;ZLandroidx/compose/foundation/layout/Arrangement$Horizontal;Landroidx/compose/ui/Alignment$Vertical;ZLandroidx/tv/foundation/PivotOffsets;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V
+HSPLkotlin/ResultKt;->access$getHasEmojiCompat(Landroidx/compose/ui/text/TextStyle;)Z
+HSPLkotlin/ResultKt;->areEqual(Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLkotlin/ResultKt;->canBeSavedToBundle(Ljava/lang/Object;)Z
+HSPLkotlin/ResultKt;->checkArgument(Ljava/lang/String;Z)V
+HSPLkotlin/ResultKt;->checkElementIndex$runtime_release(II)V
+HSPLkotlin/ResultKt;->checkNotNull$1(Ljava/lang/Object;Ljava/lang/String;)V
+HSPLkotlin/ResultKt;->checkNotNull(Ljava/lang/Object;)V
+HSPLkotlin/ResultKt;->checkNotNull(Ljava/lang/Object;Ljava/lang/String;)V
+HSPLkotlin/ResultKt;->checkNotNullExpressionValue(Ljava/lang/Object;Ljava/lang/String;)V
+HSPLkotlin/ResultKt;->checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V
+HSPLkotlin/ResultKt;->compare(II)I
+HSPLkotlin/ResultKt;->constrain-4WqzIAM(JJ)J
+HSPLkotlin/ResultKt;->constrainHeight-K40F9xA(JI)I
+HSPLkotlin/ResultKt;->constrainWidth-K40F9xA(JI)I
+HSPLkotlin/ResultKt;->create(Landroid/content/Context;)Landroidx/emoji2/text/FontRequestEmojiCompatConfig;
+HSPLkotlin/ResultKt;->createCoroutineUnintercepted(Ljava/lang/Object;Lkotlin/coroutines/Continuation;Lkotlin/jvm/functions/Function2;)Lkotlin/coroutines/Continuation;
+HSPLkotlin/ResultKt;->createFailure(Ljava/lang/Throwable;)Lkotlin/Result$Failure;
+HSPLkotlin/ResultKt;->distinctUntilChanged(Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+HSPLkotlin/ResultKt;->emitAllImpl$FlowKt__ChannelsKt(Lkotlinx/coroutines/flow/FlowCollector;Lkotlinx/coroutines/channels/ProducerCoroutine;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlin/ResultKt;->ensureActive(Lkotlin/coroutines/CoroutineContext;)V
+HSPLkotlin/ResultKt;->findIndexByKey$1(Landroidx/compose/foundation/lazy/layout/LazyLayoutItemProvider;Ljava/lang/Object;I)I
+HSPLkotlin/ResultKt;->first(Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlin/ResultKt;->get(Landroid/view/View;)Landroidx/lifecycle/LifecycleOwner;
+HSPLkotlin/ResultKt;->getExclusions()Ljava/util/Set;
+HSPLkotlin/ResultKt;->getJob(Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/Job;
+HSPLkotlin/ResultKt;->getOrCreateCancellableContinuation(Lkotlin/coroutines/Continuation;)Lkotlinx/coroutines/CancellableContinuationImpl;
+HSPLkotlin/ResultKt;->getProgressionLastElement(III)I
+HSPLkotlin/ResultKt;->getSp(D)J
+HSPLkotlin/ResultKt;->getSp(I)J
+HSPLkotlin/ResultKt;->hasFontAttributes(Landroidx/compose/ui/text/SpanStyle;)Z
+HSPLkotlin/ResultKt;->intercepted(Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLkotlin/ResultKt;->isEnabled()Z
+HSPLkotlin/ResultKt;->isUnspecified--R2X_6o(J)Z
+HSPLkotlin/ResultKt;->launch$default(Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/android/HandlerContext;ILkotlin/jvm/functions/Function2;I)Lkotlinx/coroutines/StandaloneCoroutine;
+HSPLkotlin/ResultKt;->launch(Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/StandaloneCoroutine;
+HSPLkotlin/ResultKt;->lazyLayoutSemantics(Landroidx/compose/ui/Modifier;Lkotlin/reflect/KProperty0;Landroidx/tv/foundation/lazy/layout/LazyLayoutSemanticState;Landroidx/compose/foundation/gestures/Orientation;ZZLandroidx/compose/runtime/Composer;)Landroidx/compose/ui/Modifier;
+HSPLkotlin/ResultKt;->mapCapacity(I)I
+HSPLkotlin/ResultKt;->materializeModifier(Landroidx/compose/runtime/Composer;Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
+HSPLkotlin/ResultKt;->mmap(Landroid/content/Context;Landroid/net/Uri;)Ljava/nio/MappedByteBuffer;
+HSPLkotlin/ResultKt;->offset-NN6Ew-U(IIJ)J
+HSPLkotlin/ResultKt;->overscrollEffect(Landroidx/compose/runtime/Composer;)Landroidx/compose/foundation/OverscrollEffect;
+HSPLkotlin/ResultKt;->pack(FJ)J
+HSPLkotlin/ResultKt;->plus(Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlin/ResultKt;->read(Ljava/nio/MappedByteBuffer;)Landroidx/emoji2/text/flatbuffer/MetadataList;
+HSPLkotlin/ResultKt;->recoverResult(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlin/ResultKt;->requireOwner(Landroidx/compose/ui/node/LayoutNode;)Landroidx/compose/ui/node/Owner;
+HSPLkotlin/ResultKt;->resolveLineHeightInPx-o2QH7mI(JFLandroidx/compose/ui/unit/Density;)F
+HSPLkotlin/ResultKt;->restoreThreadContext(Lkotlin/coroutines/CoroutineContext;Ljava/lang/Object;)V
+HSPLkotlin/ResultKt;->resume(Lkotlinx/coroutines/DispatchedTask;Lkotlin/coroutines/Continuation;Z)V
+HSPLkotlin/ResultKt;->roundToInt(F)I
+HSPLkotlin/ResultKt;->scrollableWithPivot(Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/gestures/ScrollableState;Landroidx/compose/foundation/gestures/Orientation;Landroidx/tv/foundation/PivotOffsets;ZZ)Landroidx/compose/ui/Modifier;
+HSPLkotlin/ResultKt;->startUndispatchedOrReturn(Lkotlinx/coroutines/internal/ScopeCoroutine;Lkotlinx/coroutines/internal/ScopeCoroutine;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLkotlin/ResultKt;->stateIn(Lkotlinx/coroutines/flow/SafeFlow;Lkotlinx/coroutines/internal/ContextScope;Lkotlinx/coroutines/flow/StartedWhileSubscribed;Ljava/lang/Float;)Lkotlinx/coroutines/flow/ReadonlyStateFlow;
+HSPLkotlin/ResultKt;->systemProp$default(Ljava/lang/String;IIII)I
+HSPLkotlin/ResultKt;->systemProp(Ljava/lang/String;JJJ)J
+HSPLkotlin/ResultKt;->threadContextElements(Lkotlin/coroutines/CoroutineContext;)Ljava/lang/Object;
+HSPLkotlin/ResultKt;->throwOnFailure(Ljava/lang/Object;)V
+HSPLkotlin/ResultKt;->toSize-ozmzZPI(J)J
+HSPLkotlin/ResultKt;->ulongToDouble(J)D
+HSPLkotlin/ResultKt;->updateThreadContext(Lkotlin/coroutines/CoroutineContext;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlin/ResultKt;->withContext(Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlin/SynchronizedLazyImpl;-><init>(Lkotlin/jvm/functions/Function0;)V
+HSPLkotlin/SynchronizedLazyImpl;->getValue()Ljava/lang/Object;
+HSPLkotlin/TuplesKt;-><clinit>()V
+HSPLkotlin/TuplesKt;->CornerRadius(FF)J
+HSPLkotlin/TuplesKt;->LazyList(Landroidx/compose/ui/Modifier;Landroidx/tv/foundation/lazy/list/TvLazyListState;Landroidx/compose/foundation/layout/PaddingValuesImpl;ZZZILandroidx/tv/foundation/PivotOffsets;Landroidx/compose/ui/Alignment$Horizontal;Landroidx/compose/foundation/layout/Arrangement$Vertical;Landroidx/compose/ui/Alignment$Vertical;Landroidx/compose/foundation/layout/Arrangement$Horizontal;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;III)V
+HSPLkotlin/TuplesKt;->PillIndicatorTabRow(Ljava/util/List;ILkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V
+HSPLkotlin/TuplesKt;->Rect-tz77jQw(JJ)Landroidx/compose/ui/geometry/Rect;
+HSPLkotlin/TuplesKt;->ScrollPositionUpdater(Lkotlin/jvm/functions/Function0;Landroidx/tv/foundation/lazy/list/TvLazyListState;Landroidx/compose/runtime/Composer;I)V
+HSPLkotlin/TuplesKt;->Size(FF)J
+HSPLkotlin/TuplesKt;->TopNavigation(Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V
+HSPLkotlin/TuplesKt;->access$addLayoutNodeChildren(Landroidx/compose/runtime/collection/MutableVector;Landroidx/compose/ui/Modifier$Node;)V
+HSPLkotlin/TuplesKt;->access$insertEntryAtIndex([Ljava/lang/Object;ILjava/lang/Object;Ljava/lang/Object;)[Ljava/lang/Object;
+HSPLkotlin/TuplesKt;->access$pop(Landroidx/compose/runtime/collection/MutableVector;)Landroidx/compose/ui/Modifier$Node;
+HSPLkotlin/TuplesKt;->access$removeRange(Ljava/util/ArrayList;II)V
+HSPLkotlin/TuplesKt;->asLayoutModifierNode(Landroidx/compose/ui/Modifier$Node;)Landroidx/compose/ui/node/LayoutModifierNode;
+HSPLkotlin/TuplesKt;->autoInvalidateInsertedNode(Landroidx/compose/ui/Modifier$Node;)V
+HSPLkotlin/TuplesKt;->autoInvalidateNodeIncludingDelegates(Landroidx/compose/ui/Modifier$Node;II)V
+HSPLkotlin/TuplesKt;->autoInvalidateNodeSelf(Landroidx/compose/ui/Modifier$Node;II)V
+HSPLkotlin/TuplesKt;->autoInvalidateUpdatedNode(Landroidx/compose/ui/Modifier$Node;)V
+HSPLkotlin/TuplesKt;->beforeCheckcastToFunctionOfArity(ILjava/lang/Object;)V
+HSPLkotlin/TuplesKt;->binarySearch([II)I
+HSPLkotlin/TuplesKt;->bitsForSlot(II)I
+HSPLkotlin/TuplesKt;->calculateLazyLayoutPinnedIndices(Landroidx/compose/foundation/lazy/layout/LazyLayoutItemProvider;Landroidx/compose/foundation/lazy/layout/LazyLayoutPinnedItemList;Landroidx/compose/runtime/Stack;)Ljava/util/List;
+HSPLkotlin/TuplesKt;->calculateNodeKindSetFrom(Landroidx/compose/ui/Modifier$Element;)I
+HSPLkotlin/TuplesKt;->calculateNodeKindSetFrom(Landroidx/compose/ui/Modifier$Node;)I
+HSPLkotlin/TuplesKt;->calculateNodeKindSetFromIncludingDelegates(Landroidx/compose/ui/Modifier$Node;)I
+HSPLkotlin/TuplesKt;->checkRadix(I)V
+HSPLkotlin/TuplesKt;->coerceIn(DDD)D
+HSPLkotlin/TuplesKt;->coerceIn(FFF)F
+HSPLkotlin/TuplesKt;->coerceIn(III)I
+HSPLkotlin/TuplesKt;->compareValues(Ljava/lang/Comparable;Ljava/lang/Comparable;)I
+HSPLkotlin/TuplesKt;->composableLambda(Landroidx/compose/runtime/Composer;ILkotlin/jvm/internal/Lambda;)Landroidx/compose/runtime/internal/ComposableLambdaImpl;
+HSPLkotlin/TuplesKt;->composableLambdaInstance(ILkotlin/jvm/internal/Lambda;Z)Landroidx/compose/runtime/internal/ComposableLambdaImpl;
+HSPLkotlin/TuplesKt;->currentValueOf(Landroidx/compose/ui/node/CompositionLocalConsumerModifierNode;Landroidx/compose/runtime/ProvidableCompositionLocal;)Ljava/lang/Object;
+HSPLkotlin/TuplesKt;->findLocation(ILjava/util/List;)I
+HSPLkotlin/TuplesKt;->findSegmentInternal(Lkotlinx/coroutines/internal/Segment;JLkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLkotlin/TuplesKt;->get(Lkotlin/coroutines/CoroutineContext$Element;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLkotlin/TuplesKt;->getCenter-uvyYCjk(J)J
+HSPLkotlin/TuplesKt;->getEllipsizedLeftPadding(Landroid/text/Layout;ILandroid/graphics/Paint;)F
+HSPLkotlin/TuplesKt;->getEllipsizedRightPadding(Landroid/text/Layout;ILandroid/graphics/Paint;)F
+HSPLkotlin/TuplesKt;->getIncludeSelfInTraversal-H91voCI(I)Z
+HSPLkotlin/TuplesKt;->getLastIndex(Ljava/util/List;)I
+HSPLkotlin/TuplesKt;->invalidateDraw(Landroidx/compose/ui/node/DrawModifierNode;)V
+HSPLkotlin/TuplesKt;->invalidateMeasurement(Landroidx/compose/ui/node/LayoutModifierNode;)V
+HSPLkotlin/TuplesKt;->invalidateSemantics(Landroidx/compose/ui/node/SemanticsModifierNode;)V
+HSPLkotlin/TuplesKt;->isWhitespace(C)Z
+HSPLkotlin/TuplesKt;->lazy(Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy;
+HSPLkotlin/TuplesKt;->listOf(Ljava/lang/Object;)Ljava/util/List;
+HSPLkotlin/TuplesKt;->listOf([Ljava/lang/Object;)Ljava/util/List;
+HSPLkotlin/TuplesKt;->minusKey(Lkotlin/coroutines/CoroutineContext$Element;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlin/TuplesKt;->observeReads(Landroidx/compose/ui/Modifier$Node;Lkotlin/jvm/functions/Function0;)V
+HSPLkotlin/TuplesKt;->painterResource(ILandroidx/compose/runtime/Composer;)Landroidx/compose/ui/graphics/painter/Painter;
+HSPLkotlin/TuplesKt;->replacableWith(Landroidx/compose/runtime/RecomposeScope;Landroidx/compose/runtime/RecomposeScopeImpl;)Z
+HSPLkotlin/TuplesKt;->requireCoordinator-64DMado(Landroidx/compose/ui/node/DelegatableNode;I)Landroidx/compose/ui/node/NodeCoordinator;
+HSPLkotlin/TuplesKt;->requireLayoutNode(Landroidx/compose/ui/node/DelegatableNode;)Landroidx/compose/ui/node/LayoutNode;
+HSPLkotlin/TuplesKt;->requireOwner(Landroidx/compose/ui/node/DelegatableNode;)Landroidx/compose/ui/node/Owner;
+HSPLkotlin/TuplesKt;->resolveDefaults(Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/unit/LayoutDirection;)Landroidx/compose/ui/text/TextStyle;
+HSPLkotlin/TuplesKt;->runtimeCheck(Z)V
+HSPLkotlin/TuplesKt;->until(II)Lkotlin/ranges/IntRange;
+HSPLkotlin/ULong$Companion;-><init>()V
+HSPLkotlin/ULong$Companion;-><init>(I)V
+HSPLkotlin/ULong$Companion;-><init>(II)V
+HSPLkotlin/ULong$Companion;->checkElementIndex$kotlin_stdlib(II)V
+HSPLkotlin/ULong$Companion;->computeScaleFactor-H7hwNQA(JJ)J
+HSPLkotlin/ULong$Companion;->dispatch$lifecycle_runtime_release(Landroid/app/Activity;Landroidx/lifecycle/Lifecycle$Event;)V
+HSPLkotlin/ULong$Companion;->getHolderForHierarchy(Landroid/view/View;)Landroidx/metrics/performance/PerformanceMetricsState$Holder;
+HSPLkotlin/ULong$Companion;->injectIfNeededIn(Landroid/app/Activity;)V
+HSPLkotlin/UNINITIALIZED_VALUE;-><clinit>()V
+HSPLkotlin/Unit;-><clinit>()V
+HSPLkotlin/UnsafeLazyImpl;-><init>(Lkotlin/jvm/functions/Function0;)V
+HSPLkotlin/UnsafeLazyImpl;->getValue()Ljava/lang/Object;
+HSPLkotlin/collections/AbstractCollection;->isEmpty()Z
+HSPLkotlin/collections/AbstractCollection;->size()I
+HSPLkotlin/collections/AbstractList;->equals(Ljava/lang/Object;)Z
+HSPLkotlin/collections/AbstractMap$toString$1;-><init>(ILjava/lang/Object;)V
+HSPLkotlin/collections/AbstractMap$toString$1;->invoke()Landroidx/compose/runtime/DisposableEffectResult;
+HSPLkotlin/collections/AbstractMap$toString$1;->invoke(F)Ljava/lang/Float;
+HSPLkotlin/collections/AbstractMap$toString$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlin/collections/AbstractMap$toString$1;->invoke(Ljava/lang/Object;)V
+HSPLkotlin/collections/AbstractMap;->entrySet()Ljava/util/Set;
+HSPLkotlin/collections/AbstractMap;->equals(Ljava/lang/Object;)Z
+HSPLkotlin/collections/AbstractMap;->size()I
+HSPLkotlin/collections/AbstractMutableList;-><init>()V
+HSPLkotlin/collections/AbstractMutableList;->size()I
+HSPLkotlin/collections/AbstractSet;->equals(Ljava/lang/Object;)Z
+HSPLkotlin/collections/ArrayDeque;-><clinit>()V
+HSPLkotlin/collections/ArrayDeque;-><init>()V
+HSPLkotlin/collections/ArrayDeque;->addLast(Ljava/lang/Object;)V
+HSPLkotlin/collections/ArrayDeque;->ensureCapacity(I)V
+HSPLkotlin/collections/ArrayDeque;->first()Ljava/lang/Object;
+HSPLkotlin/collections/ArrayDeque;->get(I)Ljava/lang/Object;
+HSPLkotlin/collections/ArrayDeque;->getSize()I
+HSPLkotlin/collections/ArrayDeque;->incremented(I)I
+HSPLkotlin/collections/ArrayDeque;->isEmpty()Z
+HSPLkotlin/collections/ArrayDeque;->positiveMod(I)I
+HSPLkotlin/collections/ArrayDeque;->removeFirst()Ljava/lang/Object;
+HSPLkotlin/collections/ArraysKt___ArraysKt;->asList([Ljava/lang/Object;)Ljava/util/List;
+HSPLkotlin/collections/ArraysKt___ArraysKt;->collectionSizeOrDefault(Ljava/lang/Iterable;)I
+HSPLkotlin/collections/ArraysKt___ArraysKt;->copyInto$default([I[III)V
+HSPLkotlin/collections/ArraysKt___ArraysKt;->copyInto$default([Ljava/lang/Object;[Ljava/lang/Object;III)V
+HSPLkotlin/collections/ArraysKt___ArraysKt;->copyInto([I[IIII)V
+HSPLkotlin/collections/ArraysKt___ArraysKt;->copyInto([Ljava/lang/Object;[Ljava/lang/Object;III)V
+HSPLkotlin/collections/ArraysKt___ArraysKt;->fill$default([Ljava/lang/Object;)V
+HSPLkotlin/collections/ArraysKt___ArraysKt;->fill(II[Ljava/lang/Object;)V
+HSPLkotlin/collections/ArraysKt___ArraysKt;->indexOf([Ljava/lang/Object;Ljava/lang/Object;)I
+HSPLkotlin/collections/CollectionsKt__MutableCollectionsJVMKt;->sortWith(Ljava/util/List;Ljava/util/Comparator;)V
+HSPLkotlin/collections/CollectionsKt__ReversedViewsKt;->addAll(Ljava/lang/Iterable;Ljava/util/Collection;)V
+HSPLkotlin/collections/CollectionsKt___CollectionsKt;->firstOrNull(Ljava/util/List;)Ljava/lang/Object;
+HSPLkotlin/collections/CollectionsKt___CollectionsKt;->last(Ljava/util/List;)Ljava/lang/Object;
+HSPLkotlin/collections/CollectionsKt___CollectionsKt;->plus(Ljava/util/List;Ljava/io/Serializable;)Ljava/util/ArrayList;
+HSPLkotlin/collections/CollectionsKt___CollectionsKt;->toIntArray(Ljava/util/ArrayList;)[I
+HSPLkotlin/collections/EmptyList;-><clinit>()V
+HSPLkotlin/collections/EmptyList;->contains(Ljava/lang/Object;)Z
+HSPLkotlin/collections/EmptyList;->isEmpty()Z
+HSPLkotlin/collections/EmptyList;->size()I
+HSPLkotlin/collections/EmptyList;->toArray()[Ljava/lang/Object;
+HSPLkotlin/collections/EmptyMap;-><clinit>()V
+HSPLkotlin/collections/EmptyMap;->isEmpty()Z
+HSPLkotlin/collections/EmptyMap;->size()I
+HSPLkotlin/coroutines/AbstractCoroutineContextElement;-><init>(Lkotlin/coroutines/CoroutineContext$Key;)V
+HSPLkotlin/coroutines/AbstractCoroutineContextElement;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLkotlin/coroutines/AbstractCoroutineContextElement;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLkotlin/coroutines/AbstractCoroutineContextElement;->getKey()Lkotlin/coroutines/CoroutineContext$Key;
+HSPLkotlin/coroutines/AbstractCoroutineContextElement;->minusKey(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlin/coroutines/AbstractCoroutineContextElement;->plus(Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlin/coroutines/AbstractCoroutineContextKey;-><init>(Lkotlin/coroutines/CoroutineContext$Key;Lkotlin/jvm/functions/Function1;)V
+HSPLkotlin/coroutines/CombinedContext;-><init>(Lkotlin/coroutines/CoroutineContext$Element;Lkotlin/coroutines/CoroutineContext;)V
+HSPLkotlin/coroutines/CombinedContext;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLkotlin/coroutines/CombinedContext;->minusKey(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlin/coroutines/CombinedContext;->plus(Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlin/coroutines/CoroutineContext$plus$1;-><clinit>()V
+HSPLkotlin/coroutines/CoroutineContext$plus$1;-><init>(I)V
+HSPLkotlin/coroutines/CoroutineContext$plus$1;->invoke(Landroidx/compose/runtime/Composer;I)V
+HSPLkotlin/coroutines/CoroutineContext$plus$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlin/coroutines/EmptyCoroutineContext;-><clinit>()V
+HSPLkotlin/coroutines/EmptyCoroutineContext;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLkotlin/coroutines/EmptyCoroutineContext;->plus(Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlin/coroutines/intrinsics/CoroutineSingletons;-><clinit>()V
+HSPLkotlin/coroutines/intrinsics/CoroutineSingletons;-><init>(ILjava/lang/String;)V
+HSPLkotlin/coroutines/jvm/internal/BaseContinuationImpl;-><init>(Lkotlin/coroutines/Continuation;)V
+HSPLkotlin/coroutines/jvm/internal/BaseContinuationImpl;->resumeWith(Ljava/lang/Object;)V
+HSPLkotlin/coroutines/jvm/internal/CompletedContinuation;-><clinit>()V
+HSPLkotlin/coroutines/jvm/internal/ContinuationImpl;-><init>(Lkotlin/coroutines/Continuation;)V
+HSPLkotlin/coroutines/jvm/internal/ContinuationImpl;-><init>(Lkotlin/coroutines/Continuation;Lkotlin/coroutines/CoroutineContext;)V
+HSPLkotlin/coroutines/jvm/internal/ContinuationImpl;->getContext()Lkotlin/coroutines/CoroutineContext;
+HSPLkotlin/coroutines/jvm/internal/ContinuationImpl;->releaseIntercepted()V
+HSPLkotlin/coroutines/jvm/internal/SuspendLambda;-><init>(ILkotlin/coroutines/Continuation;)V
+HSPLkotlin/coroutines/jvm/internal/SuspendLambda;->getArity()I
+HSPLkotlin/jvm/internal/ArrayIterator;-><init>(ILjava/lang/Object;)V
+HSPLkotlin/jvm/internal/ArrayIterator;->hasNext()Z
+HSPLkotlin/jvm/internal/ArrayIterator;->next()Ljava/lang/Object;
+HSPLkotlin/jvm/internal/CallableReference$NoReceiver;-><clinit>()V
+HSPLkotlin/jvm/internal/CallableReference;-><init>(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Z)V
+HSPLkotlin/jvm/internal/ClassReference;-><clinit>()V
+HSPLkotlin/jvm/internal/ClassReference;-><init>(Ljava/lang/Class;)V
+HSPLkotlin/jvm/internal/ClassReference;->getJClass()Ljava/lang/Class;
+HSPLkotlin/jvm/internal/FunctionReferenceImpl;-><init>(ILjava/lang/Class;Ljava/lang/String;Ljava/lang/String;I)V
+HSPLkotlin/jvm/internal/Lambda;-><init>(I)V
+HSPLkotlin/jvm/internal/Lambda;->getArity()I
+HSPLkotlin/jvm/internal/PropertyReference0Impl;->invoke()Ljava/lang/Object;
+HSPLkotlin/jvm/internal/PropertyReference;-><init>(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;I)V
+HSPLkotlin/jvm/internal/PropertyReference;->equals(Ljava/lang/Object;)Z
+HSPLkotlin/jvm/internal/Reflection;-><clinit>()V
+HSPLkotlin/jvm/internal/Reflection;->getOrCreateKotlinClass(Ljava/lang/Class;)Lkotlin/jvm/internal/ClassReference;
+HSPLkotlin/random/FallbackThreadLocalRandom$implStorage$1;-><init>(I)V
+HSPLkotlin/ranges/IntProgression;-><init>(III)V
+HSPLkotlin/ranges/IntProgression;->iterator()Ljava/util/Iterator;
+HSPLkotlin/ranges/IntProgressionIterator;-><init>(III)V
+HSPLkotlin/ranges/IntProgressionIterator;->hasNext()Z
+HSPLkotlin/ranges/IntProgressionIterator;->nextInt()I
+HSPLkotlin/ranges/IntRange;-><clinit>()V
+HSPLkotlin/ranges/IntRange;-><init>(II)V
+HSPLkotlin/ranges/IntRange;->equals(Ljava/lang/Object;)Z
+HSPLkotlin/ranges/IntRange;->isEmpty()Z
+HSPLkotlin/sequences/ConstrainedOnceSequence;-><init>(Lkotlin/sequences/SequencesKt__SequencesKt$asSequence$$inlined$Sequence$1;)V
+HSPLkotlin/sequences/ConstrainedOnceSequence;->iterator()Ljava/util/Iterator;
+HSPLkotlin/sequences/FilteringSequence$iterator$1;-><init>(Lkotlin/sequences/FilteringSequence;)V
+HSPLkotlin/sequences/FilteringSequence$iterator$1;->calcNext()V
+HSPLkotlin/sequences/FilteringSequence$iterator$1;->hasNext()Z
+HSPLkotlin/sequences/FilteringSequence$iterator$1;->next()Ljava/lang/Object;
+HSPLkotlin/sequences/FilteringSequence;-><init>(Lkotlin/sequences/GeneratorSequence;)V
+HSPLkotlin/sequences/GeneratorSequence$iterator$1;-><init>(Lkotlin/sequences/GeneratorSequence;)V
+HSPLkotlin/sequences/GeneratorSequence$iterator$1;->calcNext()V
+HSPLkotlin/sequences/GeneratorSequence$iterator$1;->hasNext()Z
+HSPLkotlin/sequences/GeneratorSequence$iterator$1;->next()Ljava/lang/Object;
+HSPLkotlin/sequences/GeneratorSequence;-><init>(Landroidx/compose/ui/node/LayoutNode$_foldedChildren$1;Lkotlin/jvm/functions/Function1;)V
+HSPLkotlin/sequences/GeneratorSequence;-><init>(Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)V
+HSPLkotlin/sequences/GeneratorSequence;->iterator()Ljava/util/Iterator;
+HSPLkotlin/sequences/SequencesKt;->firstOrNull(Lkotlin/sequences/FilteringSequence;)Ljava/lang/Object;
+HSPLkotlin/sequences/SequencesKt;->mapNotNull(Lkotlin/sequences/Sequence;Lkotlinx/coroutines/CoroutineDispatcher$Key$1;)Lkotlin/sequences/FilteringSequence;
+HSPLkotlin/sequences/SequencesKt;->toList(Lkotlin/sequences/Sequence;)Ljava/util/List;
+HSPLkotlin/sequences/SequencesKt__SequencesKt$asSequence$$inlined$Sequence$1;-><init>(ILjava/lang/Object;)V
+HSPLkotlin/sequences/SequencesKt__SequencesKt$asSequence$$inlined$Sequence$1;->iterator()Ljava/util/Iterator;
+HSPLkotlin/sequences/TransformingSequence$iterator$1;-><init>(Lkotlin/sequences/GeneratorSequence;)V
+HSPLkotlin/sequences/TransformingSequence$iterator$1;->hasNext()Z
+HSPLkotlin/sequences/TransformingSequence$iterator$1;->next()Ljava/lang/Object;
+HSPLkotlin/text/StringsKt__IndentKt$getIndentFunction$2;-><init>(ILjava/lang/String;)V
+HSPLkotlin/text/StringsKt__RegexExtensionsKt;->generateSequence(Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lkotlin/sequences/Sequence;
+HSPLkotlin/text/StringsKt__StringsKt;->endsWith$default(Ljava/lang/CharSequence;Ljava/lang/String;)Z
+HSPLkotlin/text/StringsKt__StringsKt;->getLastIndex(Ljava/lang/CharSequence;)I
+HSPLkotlin/text/StringsKt__StringsKt;->indexOf(Ljava/lang/CharSequence;Ljava/lang/String;IZ)I
+HSPLkotlin/text/StringsKt__StringsKt;->isBlank(Ljava/lang/CharSequence;)Z
+HSPLkotlin/text/StringsKt__StringsKt;->replace$default(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+HSPLkotlin/text/StringsKt__StringsKt;->substringAfterLast$default(Ljava/lang/String;)Ljava/lang/String;
+HSPLkotlin/text/StringsKt___StringsKt;->last(Ljava/lang/CharSequence;)C
+HSPLkotlinx/coroutines/AbstractCoroutine;-><init>(Lkotlin/coroutines/CoroutineContext;Z)V
+HSPLkotlinx/coroutines/AbstractCoroutine;->getContext()Lkotlin/coroutines/CoroutineContext;
+HSPLkotlinx/coroutines/AbstractCoroutine;->getCoroutineContext()Lkotlin/coroutines/CoroutineContext;
+HSPLkotlinx/coroutines/AbstractCoroutine;->isActive()Z
+HSPLkotlinx/coroutines/AbstractCoroutine;->onCancelled(Ljava/lang/Throwable;Z)V
+HSPLkotlinx/coroutines/AbstractCoroutine;->onCompleted(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/AbstractCoroutine;->onCompletionInternal(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/AbstractCoroutine;->resumeWith(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/AbstractCoroutine;->start$enumunboxing$(ILkotlinx/coroutines/AbstractCoroutine;Lkotlin/jvm/functions/Function2;)V
+HSPLkotlinx/coroutines/Active;-><clinit>()V
+HSPLkotlinx/coroutines/BlockingEventLoop;-><init>(Ljava/lang/Thread;)V
+HSPLkotlinx/coroutines/CancelHandler;-><init>()V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;-><clinit>()V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;-><init>(ILkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->callCancelHandler(Lkotlinx/coroutines/CancelHandler;Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->callSegmentOnCancellation(Lkotlinx/coroutines/internal/Segment;Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->cancel(Ljava/lang/Throwable;)Z
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->completeResume(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->detachChild$kotlinx_coroutines_core()V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->dispatchResume(I)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->getContinuationCancellationCause(Lkotlinx/coroutines/JobSupport;)Ljava/lang/Throwable;
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->getDelegate$kotlinx_coroutines_core()Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->getExceptionalResult$kotlinx_coroutines_core(Ljava/lang/Object;)Ljava/lang/Throwable;
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->getResult()Ljava/lang/Object;
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->getSuccessfulResult$kotlinx_coroutines_core(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->initCancellability()V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->installParentHandle()Lkotlinx/coroutines/DisposableHandle;
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->invokeOnCancellation(Lkotlin/jvm/functions/Function1;)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->invokeOnCancellation(Lkotlinx/coroutines/internal/Segment;I)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->invokeOnCancellationImpl(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->isReusable()Z
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->releaseClaimedReusableContinuation$kotlinx_coroutines_core()V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->resumeImpl(Ljava/lang/Object;ILkotlin/jvm/functions/Function1;)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->resumeWith(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->resumedState(Lkotlinx/coroutines/NotCompleted;Ljava/lang/Object;ILkotlin/jvm/functions/Function1;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->takeState$kotlinx_coroutines_core()Ljava/lang/Object;
+HSPLkotlinx/coroutines/CancellableContinuationImpl;->tryResume(Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/internal/Symbol;
+HSPLkotlinx/coroutines/CancelledContinuation;-><clinit>()V
+HSPLkotlinx/coroutines/CancelledContinuation;-><init>(Lkotlin/coroutines/Continuation;Ljava/lang/Throwable;Z)V
+HSPLkotlinx/coroutines/ChildContinuation;-><init>(Lkotlinx/coroutines/CancellableContinuationImpl;)V
+HSPLkotlinx/coroutines/ChildContinuation;->invoke(Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/ChildHandleNode;-><init>(Lkotlinx/coroutines/JobSupport;)V
+HSPLkotlinx/coroutines/ChildHandleNode;->childCancelled(Ljava/lang/Throwable;)Z
+HSPLkotlinx/coroutines/ChildHandleNode;->invoke(Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/CompletedContinuation;-><init>(Ljava/lang/Object;Lkotlinx/coroutines/CancelHandler;Lkotlin/jvm/functions/Function1;Ljava/lang/Object;Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/CompletedContinuation;-><init>(Ljava/lang/Object;Lkotlinx/coroutines/CancelHandler;Lkotlin/jvm/functions/Function1;Ljava/util/concurrent/CancellationException;I)V
+HSPLkotlinx/coroutines/CompletedExceptionally;-><clinit>()V
+HSPLkotlinx/coroutines/CompletedExceptionally;-><init>(Ljava/lang/Throwable;Z)V
+HSPLkotlinx/coroutines/CoroutineContextKt$foldCopies$1;-><clinit>()V
+HSPLkotlinx/coroutines/CoroutineContextKt$foldCopies$1;-><init>(I)V
+HSPLkotlinx/coroutines/CoroutineContextKt$foldCopies$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/CoroutineDispatcher$Key$1;-><clinit>()V
+HSPLkotlinx/coroutines/CoroutineDispatcher$Key$1;-><init>(I)V
+HSPLkotlinx/coroutines/CoroutineDispatcher$Key$1;->invoke(Landroid/view/View;)Landroid/view/View;
+HSPLkotlinx/coroutines/CoroutineDispatcher$Key$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/CoroutineDispatcher$Key;-><init>(I)V
+HSPLkotlinx/coroutines/CoroutineDispatcher;-><clinit>()V
+HSPLkotlinx/coroutines/CoroutineDispatcher;-><init>()V
+HSPLkotlinx/coroutines/CoroutineDispatcher;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLkotlinx/coroutines/CoroutineDispatcher;->isDispatchNeeded()Z
+HSPLkotlinx/coroutines/CoroutineDispatcher;->minusKey(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlinx/coroutines/DefaultExecutor;-><clinit>()V
+HSPLkotlinx/coroutines/DefaultExecutorKt;-><clinit>()V
+HSPLkotlinx/coroutines/DispatchedTask;-><init>(I)V
+HSPLkotlinx/coroutines/DispatchedTask;->getExceptionalResult$kotlinx_coroutines_core(Ljava/lang/Object;)Ljava/lang/Throwable;
+HSPLkotlinx/coroutines/DispatchedTask;->getSuccessfulResult$kotlinx_coroutines_core(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/DispatchedTask;->handleFatalException(Ljava/lang/Throwable;Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/DispatchedTask;->run()V
+HSPLkotlinx/coroutines/Dispatchers;-><clinit>()V
+HSPLkotlinx/coroutines/Empty;-><init>(Z)V
+HSPLkotlinx/coroutines/Empty;->getList()Lkotlinx/coroutines/NodeList;
+HSPLkotlinx/coroutines/Empty;->isActive()Z
+HSPLkotlinx/coroutines/EventLoopImplBase;-><clinit>()V
+HSPLkotlinx/coroutines/EventLoopImplBase;-><init>()V
+HSPLkotlinx/coroutines/EventLoopImplPlatform;-><init>()V
+HSPLkotlinx/coroutines/EventLoopImplPlatform;->decrementUseCount(Z)V
+HSPLkotlinx/coroutines/EventLoopImplPlatform;->incrementUseCount(Z)V
+HSPLkotlinx/coroutines/EventLoopImplPlatform;->isUnconfinedLoopActive()Z
+HSPLkotlinx/coroutines/EventLoopImplPlatform;->processUnconfinedEvent()Z
+HSPLkotlinx/coroutines/ExecutorCoroutineDispatcher$Key$1;-><clinit>()V
+HSPLkotlinx/coroutines/ExecutorCoroutineDispatcher$Key$1;-><init>(I)V
+HSPLkotlinx/coroutines/ExecutorCoroutineDispatcher$Key$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/ExecutorCoroutineDispatcher;-><clinit>()V
+HSPLkotlinx/coroutines/GlobalScope;-><clinit>()V
+HSPLkotlinx/coroutines/GlobalScope;->getCoroutineContext()Lkotlin/coroutines/CoroutineContext;
+HSPLkotlinx/coroutines/InvokeOnCancel;-><init>(ILjava/lang/Object;)V
+HSPLkotlinx/coroutines/InvokeOnCancel;->invoke(Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/InvokeOnCompletion;-><init>(ILjava/lang/Object;)V
+HSPLkotlinx/coroutines/JobImpl;-><init>(Lkotlinx/coroutines/Job;)V
+HSPLkotlinx/coroutines/JobImpl;->getHandlesException$kotlinx_coroutines_core()Z
+HSPLkotlinx/coroutines/JobImpl;->getOnCancelComplete$kotlinx_coroutines_core()Z
+HSPLkotlinx/coroutines/JobNode;-><init>()V
+HSPLkotlinx/coroutines/JobNode;->dispose()V
+HSPLkotlinx/coroutines/JobNode;->getJob()Lkotlinx/coroutines/JobSupport;
+HSPLkotlinx/coroutines/JobNode;->getList()Lkotlinx/coroutines/NodeList;
+HSPLkotlinx/coroutines/JobNode;->isActive()Z
+HSPLkotlinx/coroutines/JobSupport$ChildCompletion;-><init>(Lkotlinx/coroutines/JobSupport;Lkotlinx/coroutines/JobSupport$Finishing;Lkotlinx/coroutines/ChildHandleNode;Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/JobSupport$ChildCompletion;->invoke(Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/JobSupport$Finishing;-><clinit>()V
+HSPLkotlinx/coroutines/JobSupport$Finishing;-><init>(Lkotlinx/coroutines/NodeList;Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/JobSupport$Finishing;->addExceptionLocked(Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/JobSupport$Finishing;->getList()Lkotlinx/coroutines/NodeList;
+HSPLkotlinx/coroutines/JobSupport$Finishing;->getRootCause()Ljava/lang/Throwable;
+HSPLkotlinx/coroutines/JobSupport$Finishing;->isCancelling()Z
+HSPLkotlinx/coroutines/JobSupport$Finishing;->isCompleting()Z
+HSPLkotlinx/coroutines/JobSupport$Finishing;->sealLocked(Ljava/lang/Throwable;)Ljava/util/ArrayList;
+HSPLkotlinx/coroutines/JobSupport$addLastAtomic$$inlined$addLastIf$1;-><init>(Lkotlinx/coroutines/internal/LockFreeLinkedListNode;Lkotlinx/coroutines/JobSupport;Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/JobSupport$addLastAtomic$$inlined$addLastIf$1;->complete(Ljava/lang/Object;Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/JobSupport$addLastAtomic$$inlined$addLastIf$1;->prepare(Ljava/lang/Object;)Lkotlinx/coroutines/internal/Symbol;
+HSPLkotlinx/coroutines/JobSupport;-><clinit>()V
+HSPLkotlinx/coroutines/JobSupport;-><init>(Z)V
+HSPLkotlinx/coroutines/JobSupport;->addLastAtomic(Ljava/lang/Object;Lkotlinx/coroutines/NodeList;Lkotlinx/coroutines/JobNode;)Z
+HSPLkotlinx/coroutines/JobSupport;->afterCompletion(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/JobSupport;->afterResume(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/JobSupport;->cancel(Ljava/util/concurrent/CancellationException;)V
+HSPLkotlinx/coroutines/JobSupport;->cancelImpl$kotlinx_coroutines_core(Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/JobSupport;->cancelInternal(Ljava/util/concurrent/CancellationException;)V
+HSPLkotlinx/coroutines/JobSupport;->cancelParent(Ljava/lang/Throwable;)Z
+HSPLkotlinx/coroutines/JobSupport;->childCancelled(Ljava/lang/Throwable;)Z
+HSPLkotlinx/coroutines/JobSupport;->completeStateFinalization(Lkotlinx/coroutines/Incomplete;Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/JobSupport;->createCauseException(Ljava/lang/Object;)Ljava/lang/Throwable;
+HSPLkotlinx/coroutines/JobSupport;->finalizeFinishingState(Lkotlinx/coroutines/JobSupport$Finishing;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/JobSupport;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/JobSupport;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLkotlinx/coroutines/JobSupport;->getCancellationException()Ljava/util/concurrent/CancellationException;
+HSPLkotlinx/coroutines/JobSupport;->getFinalRootCause(Lkotlinx/coroutines/JobSupport$Finishing;Ljava/util/ArrayList;)Ljava/lang/Throwable;
+HSPLkotlinx/coroutines/JobSupport;->getKey()Lkotlin/coroutines/CoroutineContext$Key;
+HSPLkotlinx/coroutines/JobSupport;->getOnCancelComplete$kotlinx_coroutines_core()Z
+HSPLkotlinx/coroutines/JobSupport;->getOrPromoteCancellingList(Lkotlinx/coroutines/Incomplete;)Lkotlinx/coroutines/NodeList;
+HSPLkotlinx/coroutines/JobSupport;->getState$kotlinx_coroutines_core()Ljava/lang/Object;
+HSPLkotlinx/coroutines/JobSupport;->initParentJob(Lkotlinx/coroutines/Job;)V
+HSPLkotlinx/coroutines/JobSupport;->invokeOnCompletion(ZZLkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle;
+HSPLkotlinx/coroutines/JobSupport;->isActive()Z
+HSPLkotlinx/coroutines/JobSupport;->isScopedCoroutine()Z
+HSPLkotlinx/coroutines/JobSupport;->makeCompletingOnce$kotlinx_coroutines_core(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/JobSupport;->minusKey(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlinx/coroutines/JobSupport;->nextChild(Lkotlinx/coroutines/internal/LockFreeLinkedListNode;)Lkotlinx/coroutines/ChildHandleNode;
+HSPLkotlinx/coroutines/JobSupport;->notifyCancelling(Lkotlinx/coroutines/NodeList;Ljava/lang/Throwable;)V
+HSPLkotlinx/coroutines/JobSupport;->onCompletionInternal(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/JobSupport;->plus(Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+HSPLkotlinx/coroutines/JobSupport;->promoteSingleToNodeList(Lkotlinx/coroutines/JobNode;)V
+HSPLkotlinx/coroutines/JobSupport;->startInternal(Ljava/lang/Object;)I
+HSPLkotlinx/coroutines/JobSupport;->tryMakeCompleting(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/JobSupport;->tryWaitForChild(Lkotlinx/coroutines/JobSupport$Finishing;Lkotlinx/coroutines/ChildHandleNode;Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/NodeList;-><init>()V
+HSPLkotlinx/coroutines/NodeList;->getList()Lkotlinx/coroutines/NodeList;
+HSPLkotlinx/coroutines/NodeList;->isActive()Z
+HSPLkotlinx/coroutines/NodeList;->isRemoved()Z
+HSPLkotlinx/coroutines/NonDisposableHandle;-><clinit>()V
+HSPLkotlinx/coroutines/NonDisposableHandle;->dispose()V
+HSPLkotlinx/coroutines/ThreadLocalEventLoop;-><clinit>()V
+HSPLkotlinx/coroutines/ThreadLocalEventLoop;->getEventLoop$kotlinx_coroutines_core()Lkotlinx/coroutines/EventLoopImplPlatform;
+HSPLkotlinx/coroutines/Unconfined;-><clinit>()V
+HSPLkotlinx/coroutines/UndispatchedCoroutine;-><init>(Lkotlin/coroutines/Continuation;Lkotlin/coroutines/CoroutineContext;)V
+HSPLkotlinx/coroutines/UndispatchedMarker;-><clinit>()V
+HSPLkotlinx/coroutines/UndispatchedMarker;->fold(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/UndispatchedMarker;->get(Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+HSPLkotlinx/coroutines/UndispatchedMarker;->getKey()Lkotlin/coroutines/CoroutineContext$Key;
+HSPLkotlinx/coroutines/android/AndroidDispatcherFactory;-><init>()V
+HSPLkotlinx/coroutines/android/AndroidDispatcherFactory;->createDispatcher(Ljava/util/List;)Lkotlinx/coroutines/MainCoroutineDispatcher;
+HSPLkotlinx/coroutines/android/HandlerContext;-><init>(Landroid/os/Handler;)V
+HSPLkotlinx/coroutines/android/HandlerContext;-><init>(Landroid/os/Handler;Ljava/lang/String;Z)V
+HSPLkotlinx/coroutines/android/HandlerContext;->dispatch(Lkotlin/coroutines/CoroutineContext;Ljava/lang/Runnable;)V
+HSPLkotlinx/coroutines/android/HandlerContext;->isDispatchNeeded()Z
+HSPLkotlinx/coroutines/android/HandlerDispatcherKt;-><clinit>()V
+HSPLkotlinx/coroutines/android/HandlerDispatcherKt;->asHandler(Landroid/os/Looper;)Landroid/os/Handler;
+HSPLkotlinx/coroutines/channels/BufferOverflow;-><clinit>()V
+HSPLkotlinx/coroutines/channels/BufferOverflow;-><init>(ILjava/lang/String;)V
+HSPLkotlinx/coroutines/channels/BufferedChannel$BufferedChannelIterator;-><init>(Lkotlinx/coroutines/channels/BufferedChannel;)V
+HSPLkotlinx/coroutines/channels/BufferedChannel$BufferedChannelIterator;->hasNext(Lkotlin/coroutines/jvm/internal/ContinuationImpl;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/BufferedChannel$BufferedChannelIterator;->invokeOnCancellation(Lkotlinx/coroutines/internal/Segment;I)V
+HSPLkotlinx/coroutines/channels/BufferedChannel$BufferedChannelIterator;->next()Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/BufferedChannel;-><clinit>()V
+HSPLkotlinx/coroutines/channels/BufferedChannel;-><init>(ILkotlin/jvm/functions/Function1;)V
+HSPLkotlinx/coroutines/channels/BufferedChannel;->access$findSegmentSend(Lkotlinx/coroutines/channels/BufferedChannel;JLkotlinx/coroutines/channels/ChannelSegment;)Lkotlinx/coroutines/channels/ChannelSegment;
+HSPLkotlinx/coroutines/channels/BufferedChannel;->access$updateCellSend(Lkotlinx/coroutines/channels/BufferedChannel;Lkotlinx/coroutines/channels/ChannelSegment;ILjava/lang/Object;JLjava/lang/Object;Z)I
+HSPLkotlinx/coroutines/channels/BufferedChannel;->bufferOrRendezvousSend(J)Z
+HSPLkotlinx/coroutines/channels/BufferedChannel;->dropFirstElementUntilTheSpecifiedCellIsInTheBuffer(J)V
+HSPLkotlinx/coroutines/channels/BufferedChannel;->expandBuffer()V
+HSPLkotlinx/coroutines/channels/BufferedChannel;->findSegmentReceive(JLkotlinx/coroutines/channels/ChannelSegment;)Lkotlinx/coroutines/channels/ChannelSegment;
+HSPLkotlinx/coroutines/channels/BufferedChannel;->getBufferEndCounter()J
+HSPLkotlinx/coroutines/channels/BufferedChannel;->getReceiversCounter$kotlinx_coroutines_core()J
+HSPLkotlinx/coroutines/channels/BufferedChannel;->getSendersCounter$kotlinx_coroutines_core()J
+HSPLkotlinx/coroutines/channels/BufferedChannel;->incCompletedExpandBufferAttempts(J)V
+HSPLkotlinx/coroutines/channels/BufferedChannel;->isClosed(JZ)Z
+HSPLkotlinx/coroutines/channels/BufferedChannel;->isClosedForReceive()Z
+HSPLkotlinx/coroutines/channels/BufferedChannel;->isRendezvousOrUnlimited()Z
+HSPLkotlinx/coroutines/channels/BufferedChannel;->iterator()Lkotlinx/coroutines/channels/BufferedChannel$BufferedChannelIterator;
+HSPLkotlinx/coroutines/channels/BufferedChannel;->receive(Lkotlin/coroutines/jvm/internal/SuspendLambda;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/BufferedChannel;->send(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/BufferedChannel;->tryReceive-PtdJZtk()Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/BufferedChannel;->tryResumeReceiver(Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/channels/BufferedChannel;->updateCellReceive(Lkotlinx/coroutines/channels/ChannelSegment;IJLjava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/BufferedChannel;->waitExpandBufferCompletion$kotlinx_coroutines_core(J)V
+HSPLkotlinx/coroutines/channels/BufferedChannelKt$createSegmentFunction$1;-><clinit>()V
+HSPLkotlinx/coroutines/channels/BufferedChannelKt$createSegmentFunction$1;-><init>()V
+HSPLkotlinx/coroutines/channels/BufferedChannelKt$createSegmentFunction$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/BufferedChannelKt;-><clinit>()V
+HSPLkotlinx/coroutines/channels/BufferedChannelKt;->tryResume0(Lkotlinx/coroutines/CancellableContinuation;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Z
+HSPLkotlinx/coroutines/channels/Channel$Factory;-><clinit>()V
+HSPLkotlinx/coroutines/channels/Channel;-><clinit>()V
+HSPLkotlinx/coroutines/channels/ChannelSegment;-><init>(JLkotlinx/coroutines/channels/ChannelSegment;Lkotlinx/coroutines/channels/BufferedChannel;I)V
+HSPLkotlinx/coroutines/channels/ChannelSegment;->casState$kotlinx_coroutines_core(Ljava/lang/Object;ILjava/lang/Object;)Z
+HSPLkotlinx/coroutines/channels/ChannelSegment;->getNumberOfSlots()I
+HSPLkotlinx/coroutines/channels/ChannelSegment;->getState$kotlinx_coroutines_core(I)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/ChannelSegment;->onCancellation(ILkotlin/coroutines/CoroutineContext;)V
+HSPLkotlinx/coroutines/channels/ChannelSegment;->onCancelledRequest(IZ)V
+HSPLkotlinx/coroutines/channels/ChannelSegment;->retrieveElement$kotlinx_coroutines_core(I)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/ChannelSegment;->setElementLazy(ILjava/lang/Object;)V
+HSPLkotlinx/coroutines/channels/ChannelSegment;->setState$kotlinx_coroutines_core(ILkotlinx/coroutines/internal/Symbol;)V
+HSPLkotlinx/coroutines/channels/ConflatedBufferedChannel;-><init>(ILkotlinx/coroutines/channels/BufferOverflow;Lkotlin/jvm/functions/Function1;)V
+HSPLkotlinx/coroutines/channels/ConflatedBufferedChannel;->trySend-JP2dKIU(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/ConflatedBufferedChannel;->trySendImpl-Mj0NB7M(Ljava/lang/Object;Z)Ljava/lang/Object;
+HSPLkotlinx/coroutines/channels/ProducerCoroutine;-><init>(Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/channels/BufferedChannel;)V
+HSPLkotlinx/coroutines/channels/ProducerCoroutine;->iterator()Lkotlinx/coroutines/channels/BufferedChannel$BufferedChannelIterator;
+HSPLkotlinx/coroutines/channels/ProducerCoroutine;->send(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/AbstractFlow$collect$1;-><init>(Lkotlinx/coroutines/flow/SafeFlow;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/DistinctFlowImpl$collect$2$emit$1;-><init>(Lkotlinx/coroutines/flow/DistinctFlowImpl$collect$2;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/DistinctFlowImpl$collect$2;-><init>(Lkotlinx/coroutines/flow/DistinctFlowImpl;Lkotlin/jvm/internal/Ref$ObjectRef;Lkotlinx/coroutines/flow/FlowCollector;)V
+HSPLkotlinx/coroutines/flow/DistinctFlowImpl$collect$2;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/DistinctFlowImpl;-><init>(Lkotlinx/coroutines/flow/Flow;)V
+HSPLkotlinx/coroutines/flow/DistinctFlowImpl;->collect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__ChannelsKt$emitAllImpl$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$$inlined$unsafeFlow$1;-><init>(Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest;Lkotlinx/coroutines/flow/StartedWhileSubscribed$command$2;)V
+HSPLkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$$inlined$unsafeFlow$1;->collect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$1$1$emit$1;-><init>(Lkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$1$1;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$1$1;-><init>(Lkotlin/jvm/internal/Ref$BooleanRef;Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/jvm/functions/Function2;)V
+HSPLkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$1$1;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__MergeKt$mapLatest$1;-><init>(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/FlowKt__MergeKt$mapLatest$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__MergeKt$mapLatest$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__MergeKt;-><clinit>()V
+HSPLkotlinx/coroutines/flow/FlowKt__ReduceKt$first$$inlined$collectWhile$2$1;-><init>(Lkotlinx/coroutines/flow/FlowKt__ReduceKt$first$$inlined$collectWhile$2;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/FlowKt__ReduceKt$first$$inlined$collectWhile$2;-><init>(Lkotlin/jvm/functions/Function2;Lkotlin/jvm/internal/Ref$ObjectRef;)V
+HSPLkotlinx/coroutines/flow/FlowKt__ReduceKt$first$$inlined$collectWhile$2;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1$2;-><init>(Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/MutableSharedFlow;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1;-><init>(Lkotlinx/coroutines/flow/SharingStarted;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/MutableSharedFlow;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/ReadonlyStateFlow;-><init>(Lkotlinx/coroutines/flow/StateFlowImpl;)V
+HSPLkotlinx/coroutines/flow/ReadonlyStateFlow;->collect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/ReadonlyStateFlow;->getValue()Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/SafeFlow;-><init>(Lkotlin/jvm/functions/Function2;)V
+HSPLkotlinx/coroutines/flow/SafeFlow;->collect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl$collect$1;-><init>(Lkotlinx/coroutines/flow/SharedFlowImpl;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/SharedFlowImpl$collect$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;-><init>(IILkotlinx/coroutines/channels/BufferOverflow;)V
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->awaitValue(Lkotlinx/coroutines/flow/SharedFlowSlot;Lkotlinx/coroutines/flow/SharedFlowImpl$collect$1;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->cleanupTailLocked()V
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->collect$suspendImpl(Lkotlinx/coroutines/flow/SharedFlowImpl;Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/intrinsics/CoroutineSingletons;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->collect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->createSlot()Lkotlinx/coroutines/flow/internal/AbstractSharedFlowSlot;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->createSlotArray()[Lkotlinx/coroutines/flow/internal/AbstractSharedFlowSlot;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->dropOldestLocked()V
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->enqueueLocked(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->findSlotsToResumeLocked([Lkotlin/coroutines/Continuation;)[Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->getHead()J
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->growBuffer(II[Ljava/lang/Object;)[Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->tryEmit(Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->tryEmitLocked(Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->tryPeekLocked(Lkotlinx/coroutines/flow/SharedFlowSlot;)J
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->tryTakeValue(Lkotlinx/coroutines/flow/SharedFlowSlot;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->updateBufferLocked(JJJJ)V
+HSPLkotlinx/coroutines/flow/SharedFlowImpl;->updateCollectorIndexLocked$kotlinx_coroutines_core(J)[Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/SharedFlowSlot;-><init>()V
+HSPLkotlinx/coroutines/flow/SharedFlowSlot;->allocateLocked(Lkotlinx/coroutines/flow/internal/AbstractSharedFlow;)Z
+HSPLkotlinx/coroutines/flow/SharingCommand;-><clinit>()V
+HSPLkotlinx/coroutines/flow/SharingCommand;-><init>(ILjava/lang/String;)V
+HSPLkotlinx/coroutines/flow/SharingConfig;-><init>()V
+HSPLkotlinx/coroutines/flow/SharingConfig;-><init>(ILkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/channels/BufferOverflow;Lkotlinx/coroutines/flow/Flow;)V
+HSPLkotlinx/coroutines/flow/SharingConfig;->add(Ljava/lang/Object;Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/flow/SharingConfig;->contains(Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/flow/SharingConfig;->find(Ljava/lang/Object;)I
+HSPLkotlinx/coroutines/flow/SharingConfig;->remove(Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/flow/SharingConfig;->removeScope(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/flow/SharingConfig;->scopeSetAt(I)Landroidx/compose/runtime/collection/IdentityArraySet;
+HSPLkotlinx/coroutines/flow/StartedLazily;-><init>(I)V
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed$command$1;-><init>(Lkotlinx/coroutines/flow/StartedWhileSubscribed;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed$command$1;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed$command$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed$command$2;-><init>(Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed$command$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed$command$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed$command$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed;-><init>(JJ)V
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed;->command(Lkotlinx/coroutines/flow/internal/SubscriptionCountStateFlow;)Lkotlinx/coroutines/flow/Flow;
+HSPLkotlinx/coroutines/flow/StartedWhileSubscribed;->equals(Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/flow/StateFlowImpl$collect$1;-><init>(Lkotlinx/coroutines/flow/StateFlowImpl;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/StateFlowImpl$collect$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/StateFlowImpl;-><clinit>()V
+HSPLkotlinx/coroutines/flow/StateFlowImpl;-><init>(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/flow/StateFlowImpl;->collect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/StateFlowImpl;->createSlot()Lkotlinx/coroutines/flow/internal/AbstractSharedFlowSlot;
+HSPLkotlinx/coroutines/flow/StateFlowImpl;->createSlotArray()[Lkotlinx/coroutines/flow/internal/AbstractSharedFlowSlot;
+HSPLkotlinx/coroutines/flow/StateFlowImpl;->getValue()Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/StateFlowImpl;->setValue(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/flow/StateFlowImpl;->updateState(Ljava/lang/Object;Ljava/lang/Object;)Z
+HSPLkotlinx/coroutines/flow/StateFlowSlot;-><clinit>()V
+HSPLkotlinx/coroutines/flow/StateFlowSlot;->allocateLocked(Lkotlinx/coroutines/flow/internal/AbstractSharedFlow;)Z
+HSPLkotlinx/coroutines/flow/internal/AbstractSharedFlow;->allocateSlot()Lkotlinx/coroutines/flow/internal/AbstractSharedFlowSlot;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow$collect$2;-><init>(Lkotlin/coroutines/Continuation;Lkotlinx/coroutines/flow/FlowCollector;Lkotlinx/coroutines/flow/internal/ChannelFlow;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow$collect$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow$collect$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow$collect$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow$collectToFun$1;-><init>(Lkotlinx/coroutines/flow/internal/ChannelFlow;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow$collectToFun$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow$collectToFun$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow;-><init>(Lkotlin/coroutines/CoroutineContext;ILkotlinx/coroutines/channels/BufferOverflow;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlow;->fuse(Lkotlin/coroutines/CoroutineContext;ILkotlinx/coroutines/channels/BufferOverflow;)Lkotlinx/coroutines/flow/Flow;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowOperator;-><init>(ILkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/channels/BufferOverflow;Lkotlinx/coroutines/flow/Flow;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowOperator;->collect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1$2;-><init>(Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest;Lkotlinx/coroutines/flow/FlowCollector;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1$emit$1;-><init>(Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1;-><init>(Lkotlin/jvm/internal/Ref$ObjectRef;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest;Lkotlinx/coroutines/flow/FlowCollector;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3;-><init>(Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest;Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest;-><init>(Lkotlin/jvm/functions/Function3;Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;ILkotlinx/coroutines/channels/BufferOverflow;)V
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest;->create(Lkotlin/coroutines/CoroutineContext;ILkotlinx/coroutines/channels/BufferOverflow;)Lkotlinx/coroutines/flow/internal/ChannelFlow;
+HSPLkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest;->flowCollect(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/NoOpContinuation;-><clinit>()V
+HSPLkotlinx/coroutines/flow/internal/NopCollector;-><clinit>()V
+HSPLkotlinx/coroutines/flow/internal/SafeCollector;-><init>(Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/CoroutineContext;)V
+HSPLkotlinx/coroutines/flow/internal/SendingCollector;-><init>(Lkotlinx/coroutines/channels/ProducerScope;)V
+HSPLkotlinx/coroutines/flow/internal/SendingCollector;->emit(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/flow/internal/SubscriptionCountStateFlow;-><init>(I)V
+HSPLkotlinx/coroutines/internal/AtomicOp;-><clinit>()V
+HSPLkotlinx/coroutines/internal/AtomicOp;-><init>()V
+HSPLkotlinx/coroutines/internal/AtomicOp;->perform(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/internal/ConcurrentLinkedListNode;-><clinit>()V
+HSPLkotlinx/coroutines/internal/ConcurrentLinkedListNode;-><init>(Lkotlinx/coroutines/internal/ConcurrentLinkedListNode;)V
+HSPLkotlinx/coroutines/internal/ConcurrentLinkedListNode;->cleanPrev()V
+HSPLkotlinx/coroutines/internal/ConcurrentLinkedListNode;->getNext()Lkotlinx/coroutines/internal/ConcurrentLinkedListNode;
+HSPLkotlinx/coroutines/internal/ContextScope;-><init>(Lkotlin/coroutines/CoroutineContext;)V
+HSPLkotlinx/coroutines/internal/ContextScope;->getCoroutineContext()Lkotlin/coroutines/CoroutineContext;
+HSPLkotlinx/coroutines/internal/DispatchedContinuation;-><clinit>()V
+HSPLkotlinx/coroutines/internal/DispatchedContinuation;-><init>(Lkotlinx/coroutines/CoroutineDispatcher;Lkotlin/coroutines/jvm/internal/ContinuationImpl;)V
+HSPLkotlinx/coroutines/internal/DispatchedContinuation;->getContext()Lkotlin/coroutines/CoroutineContext;
+HSPLkotlinx/coroutines/internal/DispatchedContinuation;->getDelegate$kotlinx_coroutines_core()Lkotlin/coroutines/Continuation;
+HSPLkotlinx/coroutines/internal/DispatchedContinuation;->resumeWith(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/internal/DispatchedContinuation;->takeState$kotlinx_coroutines_core()Ljava/lang/Object;
+HSPLkotlinx/coroutines/internal/LimitedDispatcher;-><clinit>()V
+HSPLkotlinx/coroutines/internal/LimitedDispatcher;-><init>(Lkotlinx/coroutines/scheduling/UnlimitedIoScheduler;I)V
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode$toString$1;-><init>(ILjava/lang/Object;)V
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode;-><clinit>()V
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode;-><init>()V
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode;->correctPrev()Lkotlinx/coroutines/internal/LockFreeLinkedListNode;
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode;->finishAdd(Lkotlinx/coroutines/internal/LockFreeLinkedListNode;)V
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode;->getNext()Ljava/lang/Object;
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode;->getNextNode()Lkotlinx/coroutines/internal/LockFreeLinkedListNode;
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode;->getPrevNode()Lkotlinx/coroutines/internal/LockFreeLinkedListNode;
+HSPLkotlinx/coroutines/internal/LockFreeLinkedListNode;->isRemoved()Z
+HSPLkotlinx/coroutines/internal/LockFreeTaskQueue;-><clinit>()V
+HSPLkotlinx/coroutines/internal/LockFreeTaskQueue;-><init>()V
+HSPLkotlinx/coroutines/internal/LockFreeTaskQueueCore;-><clinit>()V
+HSPLkotlinx/coroutines/internal/LockFreeTaskQueueCore;-><init>(IZ)V
+HSPLkotlinx/coroutines/internal/MainDispatcherLoader;-><clinit>()V
+HSPLkotlinx/coroutines/internal/Removed;-><init>(Lkotlinx/coroutines/internal/LockFreeLinkedListNode;)V
+HSPLkotlinx/coroutines/internal/ResizableAtomicArray;-><init>(I)V
+HSPLkotlinx/coroutines/internal/ScopeCoroutine;-><init>(Lkotlin/coroutines/Continuation;Lkotlin/coroutines/CoroutineContext;)V
+HSPLkotlinx/coroutines/internal/ScopeCoroutine;->afterCompletion(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/internal/ScopeCoroutine;->afterResume(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/internal/ScopeCoroutine;->isScopedCoroutine()Z
+HSPLkotlinx/coroutines/internal/Segment;-><clinit>()V
+HSPLkotlinx/coroutines/internal/Segment;-><init>(JLkotlinx/coroutines/internal/Segment;I)V
+HSPLkotlinx/coroutines/internal/Segment;->decPointers$kotlinx_coroutines_core()Z
+HSPLkotlinx/coroutines/internal/Segment;->isRemoved()Z
+HSPLkotlinx/coroutines/internal/Segment;->onSlotCleaned()V
+HSPLkotlinx/coroutines/internal/Segment;->tryIncPointers$kotlinx_coroutines_core()Z
+HSPLkotlinx/coroutines/internal/Symbol;-><init>(ILjava/lang/String;)V
+HSPLkotlinx/coroutines/internal/SystemPropsKt__SystemPropsKt;-><clinit>()V
+HSPLkotlinx/coroutines/scheduling/CoroutineScheduler;-><clinit>()V
+HSPLkotlinx/coroutines/scheduling/CoroutineScheduler;-><init>(IIJLjava/lang/String;)V
+HSPLkotlinx/coroutines/scheduling/DefaultIoScheduler;-><clinit>()V
+HSPLkotlinx/coroutines/scheduling/DefaultScheduler;-><clinit>()V
+HSPLkotlinx/coroutines/scheduling/DefaultScheduler;-><init>()V
+HSPLkotlinx/coroutines/scheduling/NanoTimeSource;-><clinit>()V
+HSPLkotlinx/coroutines/scheduling/SchedulerCoroutineDispatcher;-><init>(IIJLjava/lang/String;)V
+HSPLkotlinx/coroutines/scheduling/Task;-><init>(JLkotlin/ULong$Companion;)V
+HSPLkotlinx/coroutines/scheduling/TasksKt;-><clinit>()V
+HSPLkotlinx/coroutines/scheduling/UnlimitedIoScheduler;-><clinit>()V
+HSPLkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner$resume$2;-><init>(Lkotlinx/coroutines/sync/MutexImpl;Lkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner;I)V
+HSPLkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner;-><init>(Lkotlinx/coroutines/sync/MutexImpl;Lkotlinx/coroutines/CancellableContinuationImpl;)V
+HSPLkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner;->completeResume(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner;->invokeOnCancellation(Lkotlinx/coroutines/internal/Segment;I)V
+HSPLkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner;->tryResume(Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/internal/Symbol;
+HSPLkotlinx/coroutines/sync/MutexImpl;-><clinit>()V
+HSPLkotlinx/coroutines/sync/MutexImpl;-><init>(Z)V
+HSPLkotlinx/coroutines/sync/MutexImpl;->isLocked()Z
+HSPLkotlinx/coroutines/sync/MutexImpl;->lock(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLkotlinx/coroutines/sync/MutexImpl;->unlock(Ljava/lang/Object;)V
+HSPLkotlinx/coroutines/sync/SemaphoreImpl$addAcquireToQueue$createNewSegment$1;-><clinit>()V
+HSPLkotlinx/coroutines/sync/SemaphoreImpl$addAcquireToQueue$createNewSegment$1;-><init>()V
+HSPLkotlinx/coroutines/sync/SemaphoreImpl$tryResumeNextFromQueue$createNewSegment$1;-><clinit>()V
+HSPLkotlinx/coroutines/sync/SemaphoreImpl$tryResumeNextFromQueue$createNewSegment$1;-><init>()V
+HSPLkotlinx/coroutines/sync/SemaphoreImpl;-><clinit>()V
+HSPLkotlinx/coroutines/sync/SemaphoreImpl;-><init>(I)V
+HSPLkotlinx/coroutines/sync/SemaphoreImpl;->acquire(Lkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner;)V
+HSPLkotlinx/coroutines/sync/SemaphoreImpl;->release()V
+HSPLkotlinx/coroutines/sync/SemaphoreKt;-><clinit>()V
+HSPLkotlinx/coroutines/sync/SemaphoreSegment;-><init>(JLkotlinx/coroutines/sync/SemaphoreSegment;I)V
+HSPLkotlinx/coroutines/sync/SemaphoreSegment;->getNumberOfSlots()I
+HSPLokhttp3/Headers$Builder;-><init>()V
+HSPLokhttp3/Headers$Builder;->add(I)V
+HSPLokhttp3/Headers$Builder;->takeMax()I
+HSPLokhttp3/MediaType;-><clinit>()V
+HSPLokhttp3/MediaType;->Channel$default(ILkotlinx/coroutines/channels/BufferOverflow;I)Lkotlinx/coroutines/channels/BufferedChannel;
+HSPLokhttp3/MediaType;->CompositionLocalProvider(Landroidx/compose/runtime/ProvidedValue;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
+HSPLokhttp3/MediaType;->CoroutineScope(Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/internal/ContextScope;
+HSPLokhttp3/MediaType;->LazyLayoutPinnableItem(Ljava/lang/Object;ILandroidx/compose/foundation/lazy/layout/LazyLayoutPinnedItemList;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
+HSPLokhttp3/MediaType;->LazySaveableStateHolderProvider(Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V
+HSPLokhttp3/MediaType;->Offset(FF)J
+HSPLokhttp3/MediaType;->ParagraphIntrinsics(Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/font/FontFamily$Resolver;Landroidx/compose/ui/unit/Density;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)Landroidx/compose/ui/text/platform/AndroidParagraphIntrinsics;
+HSPLokhttp3/MediaType;->RoundRect-gG7oq9Y(FFFFJ)Landroidx/compose/ui/geometry/RoundRect;
+HSPLokhttp3/MediaType;->TextRange(II)J
+HSPLokhttp3/MediaType;->access$SkippableItem-JVlU9Rs(Landroidx/compose/foundation/lazy/layout/LazyLayoutItemProvider;Ljava/lang/Object;ILjava/lang/Object;Landroidx/compose/runtime/Composer;I)V
+HSPLokhttp3/MediaType;->access$checkIndex(ILjava/util/List;)V
+HSPLokhttp3/MediaType;->access$containsMark([II)Z
+HSPLokhttp3/MediaType;->access$groupSize([II)I
+HSPLokhttp3/MediaType;->access$hasAux([II)Z
+HSPLokhttp3/MediaType;->access$isChainUpdate(Landroidx/compose/ui/node/BackwardsCompatNode;)Z
+HSPLokhttp3/MediaType;->access$isNode([II)Z
+HSPLokhttp3/MediaType;->access$nodeCount([II)I
+HSPLokhttp3/MediaType;->access$slotAnchor([II)I
+HSPLokhttp3/MediaType;->access$updateGroupSize([III)V
+HSPLokhttp3/MediaType;->access$updateNodeCount([III)V
+HSPLokhttp3/MediaType;->adapt$default(Landroidx/compose/ui/graphics/colorspace/ColorSpace;)Landroidx/compose/ui/graphics/colorspace/ColorSpace;
+HSPLokhttp3/MediaType;->cancel(Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/LifecycleDestroyedException;)V
+HSPLokhttp3/MediaType;->ceilToIntPx(F)I
+HSPLokhttp3/MediaType;->checkParallelism(I)V
+HSPLokhttp3/MediaType;->chromaticAdaptation([F[F[F)[F
+HSPLokhttp3/MediaType;->coerceIn-8ffj60Q(IJ)J
+HSPLokhttp3/MediaType;->collectIsPressedAsState(Landroidx/compose/foundation/interaction/InteractionSource;Landroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/MutableState;
+HSPLokhttp3/MediaType;->colors-u3YEpmA(JJJJJJJJJJJJLandroidx/compose/runtime/Composer;II)Landroidx/tv/material3/ToggleableSurfaceColors;
+HSPLokhttp3/MediaType;->compare(Landroidx/compose/ui/graphics/colorspace/WhitePoint;Landroidx/compose/ui/graphics/colorspace/WhitePoint;)Z
+HSPLokhttp3/MediaType;->coroutineScope(Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+HSPLokhttp3/MediaType;->countOneBits(I)I
+HSPLokhttp3/MediaType;->createFontFamilyResolver(Landroid/content/Context;)Landroidx/compose/ui/text/font/FontFamilyResolverImpl;
+HSPLokhttp3/MediaType;->foldCopies(Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/CoroutineContext;Z)Lkotlin/coroutines/CoroutineContext;
+HSPLokhttp3/MediaType;->getCharSequenceBounds(Landroid/text/TextPaint;Ljava/lang/CharSequence;II)Landroid/graphics/Rect;
+HSPLokhttp3/MediaType;->getFontFamilyResult(Landroid/content/Context;Landroidx/core/provider/FontRequest;)Landroidx/compose/ui/input/pointer/util/PointerIdArray;
+HSPLokhttp3/MediaType;->getOrNull(Landroidx/compose/ui/semantics/SemanticsConfiguration;Landroidx/compose/ui/semantics/SemanticsPropertyKey;)Ljava/lang/Object;
+HSPLokhttp3/MediaType;->getSegment-impl(Ljava/lang/Object;)Lkotlinx/coroutines/internal/Segment;
+HSPLokhttp3/MediaType;->inverse3x3([F)[F
+HSPLokhttp3/MediaType;->invokeComposable(Landroidx/compose/runtime/Composer;Lkotlin/jvm/functions/Function2;)V
+HSPLokhttp3/MediaType;->invokeOnCompletion$default(Lkotlinx/coroutines/Job;ZLkotlinx/coroutines/JobNode;I)Lkotlinx/coroutines/DisposableHandle;
+HSPLokhttp3/MediaType;->isActive(Lkotlinx/coroutines/CoroutineScope;)Z
+HSPLokhttp3/MediaType;->isClosed-impl(Ljava/lang/Object;)Z
+HSPLokhttp3/MediaType;->mul3x3([F[F)[F
+HSPLokhttp3/MediaType;->mul3x3Diag([F[F)[F
+HSPLokhttp3/MediaType;->mul3x3Float3([F[F)V
+HSPLokhttp3/MediaType;->mul3x3Float3_0([FFFF)F
+HSPLokhttp3/MediaType;->mul3x3Float3_1([FFFF)F
+HSPLokhttp3/MediaType;->mul3x3Float3_2([FFFF)F
+HSPLokhttp3/MediaType;->search(Ljava/util/ArrayList;II)I
+HSPLokhttp3/MediaType;->set-impl(Landroidx/compose/runtime/Composer;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V
+HSPLokhttp3/MediaType;->setInt-A6tL2VI(Landroidx/compose/runtime/changelist/Operations;II)V
+HSPLokhttp3/MediaType;->setObject-DKhxnng(Landroidx/compose/runtime/changelist/Operations;ILjava/lang/Object;)V
+HSPLokhttp3/MediaType;->shape(Landroidx/compose/ui/graphics/RectangleShapeKt$RectangleShape$1;Landroidx/compose/runtime/Composer;I)Landroidx/tv/material3/ToggleableSurfaceShape;
+HSPLokhttp3/MediaType;->toArray(Ljava/util/Collection;)[Ljava/lang/Object;
+HSPLokhttp3/MediaType;->updateChangedFlags(I)I
+L_COROUTINE/ArtificialStackFrames;
+Landroidx/activity/ComponentActivity$$ExternalSyntheticLambda0;
+Landroidx/activity/ComponentActivity$$ExternalSyntheticLambda1;
+Landroidx/activity/ComponentActivity$$ExternalSyntheticLambda2;
+Landroidx/activity/ComponentActivity$$ExternalSyntheticLambda3;
+Landroidx/activity/ComponentActivity$1;
+Landroidx/activity/ComponentActivity$2;
+Landroidx/activity/ComponentActivity$3;
+Landroidx/activity/ComponentActivity$4;
+Landroidx/activity/ComponentActivity$5;
+Landroidx/activity/ComponentActivity$NonConfigurationInstances;
+Landroidx/activity/ComponentActivity$ReportFullyDrawnExecutorApi16Impl;
+Landroidx/activity/ComponentActivity;
+Landroidx/activity/FullyDrawnReporter;
+Landroidx/activity/OnBackPressedDispatcher;
+Landroidx/activity/compose/ComponentActivityKt;
+Landroidx/activity/contextaware/ContextAwareHelper;
+Landroidx/activity/result/ActivityResult$1;
+Landroidx/arch/core/executor/ArchTaskExecutor;
+Landroidx/arch/core/executor/DefaultTaskExecutor$1;
+Landroidx/arch/core/executor/DefaultTaskExecutor;
+Landroidx/arch/core/internal/FastSafeIterableMap;
+Landroidx/arch/core/internal/SafeIterableMap$AscendingIterator;
+Landroidx/arch/core/internal/SafeIterableMap$Entry;
+Landroidx/arch/core/internal/SafeIterableMap$IteratorWithAdditions;
+Landroidx/arch/core/internal/SafeIterableMap$ListIterator;
+Landroidx/arch/core/internal/SafeIterableMap$SupportRemove;
+Landroidx/arch/core/internal/SafeIterableMap;
+Landroidx/collection/ArrayMap;
+Landroidx/collection/ArraySet;
+Landroidx/collection/LongSparseArray;
+Landroidx/collection/SimpleArrayMap;
+Landroidx/collection/SparseArrayCompat;
+Landroidx/compose/animation/FlingCalculator;
+Landroidx/compose/animation/FlingCalculatorKt;
+Landroidx/compose/animation/SingleValueAnimationKt;
+Landroidx/compose/animation/SplineBasedFloatDecayAnimationSpec;
+Landroidx/compose/animation/SplineBasedFloatDecayAnimationSpec_androidKt;
+Landroidx/compose/animation/core/Animatable$runAnimation$2;
+Landroidx/compose/animation/core/Animatable;
+Landroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$2;
+Landroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$3$1;
+Landroidx/compose/animation/core/AnimateAsStateKt$animateValueAsState$3;
+Landroidx/compose/animation/core/AnimateAsStateKt;
+Landroidx/compose/animation/core/Animation;
+Landroidx/compose/animation/core/AnimationEndReason$EnumUnboxingLocalUtility;
+Landroidx/compose/animation/core/AnimationResult;
+Landroidx/compose/animation/core/AnimationScope;
+Landroidx/compose/animation/core/AnimationSpec;
+Landroidx/compose/animation/core/AnimationState;
+Landroidx/compose/animation/core/AnimationVector1D;
+Landroidx/compose/animation/core/AnimationVector2D;
+Landroidx/compose/animation/core/AnimationVector3D;
+Landroidx/compose/animation/core/AnimationVector4D;
+Landroidx/compose/animation/core/AnimationVector;
+Landroidx/compose/animation/core/Animations;
+Landroidx/compose/animation/core/ComplexDouble;
+Landroidx/compose/animation/core/CubicBezierEasing;
+Landroidx/compose/animation/core/DecayAnimationSpecImpl;
+Landroidx/compose/animation/core/Easing;
+Landroidx/compose/animation/core/EasingKt$$ExternalSyntheticLambda0;
+Landroidx/compose/animation/core/EasingKt;
+Landroidx/compose/animation/core/FloatAnimationSpec;
+Landroidx/compose/animation/core/FloatDecayAnimationSpec;
+Landroidx/compose/animation/core/FloatSpringSpec;
+Landroidx/compose/animation/core/FloatTweenSpec;
+Landroidx/compose/animation/core/MutatorMutex$Mutator;
+Landroidx/compose/animation/core/MutatorMutex$mutate$2;
+Landroidx/compose/animation/core/MutatorMutex;
+Landroidx/compose/animation/core/SpringSimulation;
+Landroidx/compose/animation/core/SpringSpec;
+Landroidx/compose/animation/core/SuspendAnimationKt$animate$4;
+Landroidx/compose/animation/core/SuspendAnimationKt$animate$6;
+Landroidx/compose/animation/core/SuspendAnimationKt$animate$7;
+Landroidx/compose/animation/core/SuspendAnimationKt$animate$9;
+Landroidx/compose/animation/core/TargetBasedAnimation;
+Landroidx/compose/animation/core/TweenSpec;
+Landroidx/compose/animation/core/TwoWayConverterImpl;
+Landroidx/compose/animation/core/VectorConvertersKt;
+Landroidx/compose/animation/core/VectorizedDurationBasedAnimationSpec;
+Landroidx/compose/animation/core/VectorizedFiniteAnimationSpec;
+Landroidx/compose/animation/core/VectorizedFloatAnimationSpec;
+Landroidx/compose/animation/core/VectorizedSpringSpec;
+Landroidx/compose/animation/core/VectorizedTweenSpec;
+Landroidx/compose/animation/core/VisibilityThresholdsKt;
+Landroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect$effectModifier$1;
+Landroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect$onNewSize$1;
+Landroidx/compose/foundation/AndroidEdgeEffectOverscrollEffect;
+Landroidx/compose/foundation/AndroidOverscrollKt;
+Landroidx/compose/foundation/Api31Impl;
+Landroidx/compose/foundation/BackgroundElement;
+Landroidx/compose/foundation/BackgroundNode;
+Landroidx/compose/foundation/BorderCache;
+Landroidx/compose/foundation/BorderKt$drawRectBorder$1;
+Landroidx/compose/foundation/BorderModifierNode$drawGenericBorder$3;
+Landroidx/compose/foundation/BorderModifierNode$drawRoundRectBorder$1;
+Landroidx/compose/foundation/BorderModifierNode;
+Landroidx/compose/foundation/BorderModifierNodeElement;
+Landroidx/compose/foundation/BorderStroke;
+Landroidx/compose/foundation/ClipScrollableContainerKt;
+Landroidx/compose/foundation/DrawOverscrollModifier;
+Landroidx/compose/foundation/FocusableElement;
+Landroidx/compose/foundation/FocusableInteractionNode$emitWithFallback$1;
+Landroidx/compose/foundation/FocusableInteractionNode;
+Landroidx/compose/foundation/FocusableKt$FocusableInNonTouchModeElement$1;
+Landroidx/compose/foundation/FocusableKt;
+Landroidx/compose/foundation/FocusableNode$onFocusEvent$1;
+Landroidx/compose/foundation/FocusableNode;
+Landroidx/compose/foundation/FocusablePinnableContainerNode;
+Landroidx/compose/foundation/FocusableSemanticsNode;
+Landroidx/compose/foundation/FocusedBoundsKt;
+Landroidx/compose/foundation/FocusedBoundsNode;
+Landroidx/compose/foundation/FocusedBoundsObserverNode;
+Landroidx/compose/foundation/ImageKt$Image$1$1;
+Landroidx/compose/foundation/ImageKt$Image$1;
+Landroidx/compose/foundation/ImageKt$Image$2;
+Landroidx/compose/foundation/ImageKt;
+Landroidx/compose/foundation/Indication;
+Landroidx/compose/foundation/IndicationInstance;
+Landroidx/compose/foundation/IndicationKt$indication$2;
+Landroidx/compose/foundation/IndicationKt;
+Landroidx/compose/foundation/IndicationModifier;
+Landroidx/compose/foundation/MutatePriority;
+Landroidx/compose/foundation/MutatorMutex$Mutator;
+Landroidx/compose/foundation/MutatorMutex$mutateWith$2;
+Landroidx/compose/foundation/MutatorMutex;
+Landroidx/compose/foundation/OverscrollConfiguration;
+Landroidx/compose/foundation/OverscrollConfigurationKt;
+Landroidx/compose/foundation/OverscrollEffect;
+Landroidx/compose/foundation/ScrollKt$rememberScrollState$1$1;
+Landroidx/compose/foundation/ScrollKt$scroll$2$semantics$1$1;
+Landroidx/compose/foundation/ScrollKt$scroll$2$semantics$1;
+Landroidx/compose/foundation/ScrollKt$scroll$2;
+Landroidx/compose/foundation/ScrollState$canScrollForward$2;
+Landroidx/compose/foundation/ScrollState;
+Landroidx/compose/foundation/ScrollingLayoutElement;
+Landroidx/compose/foundation/ScrollingLayoutNode;
+Landroidx/compose/foundation/gestures/BringIntoViewRequestPriorityQueue;
+Landroidx/compose/foundation/gestures/BringIntoViewSpec$Companion$DefaultBringIntoViewSpec$1;
+Landroidx/compose/foundation/gestures/BringIntoViewSpec$Companion;
+Landroidx/compose/foundation/gestures/BringIntoViewSpec;
+Landroidx/compose/foundation/gestures/ContentInViewNode$Request;
+Landroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2$1;
+Landroidx/compose/foundation/gestures/ContentInViewNode$launchAnimation$2;
+Landroidx/compose/foundation/gestures/ContentInViewNode;
+Landroidx/compose/foundation/gestures/DefaultFlingBehavior;
+Landroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2$1;
+Landroidx/compose/foundation/gestures/DefaultScrollableState$scroll$2;
+Landroidx/compose/foundation/gestures/DefaultScrollableState$scrollScope$1;
+Landroidx/compose/foundation/gestures/DefaultScrollableState;
+Landroidx/compose/foundation/gestures/DraggableKt$awaitDrag$2;
+Landroidx/compose/foundation/gestures/DraggableNode$onAttach$1;
+Landroidx/compose/foundation/gestures/DraggableNode$pointerInputNode$1;
+Landroidx/compose/foundation/gestures/DraggableNode;
+Landroidx/compose/foundation/gestures/FlingBehavior;
+Landroidx/compose/foundation/gestures/ModifierLocalScrollableContainerProvider;
+Landroidx/compose/foundation/gestures/MouseWheelScrollNode$1;
+Landroidx/compose/foundation/gestures/MouseWheelScrollNode;
+Landroidx/compose/foundation/gestures/Orientation;
+Landroidx/compose/foundation/gestures/ScrollDraggableState;
+Landroidx/compose/foundation/gestures/ScrollScope;
+Landroidx/compose/foundation/gestures/ScrollableElement;
+Landroidx/compose/foundation/gestures/ScrollableGesturesNode$onDragStopped$1;
+Landroidx/compose/foundation/gestures/ScrollableGesturesNode;
+Landroidx/compose/foundation/gestures/ScrollableKt$DefaultScrollMotionDurationScale$1;
+Landroidx/compose/foundation/gestures/ScrollableKt$NoOpOnDragStarted$1;
+Landroidx/compose/foundation/gestures/ScrollableKt$NoOpScrollScope$1;
+Landroidx/compose/foundation/gestures/ScrollableKt$UnityDensity$1;
+Landroidx/compose/foundation/gestures/ScrollableKt;
+Landroidx/compose/foundation/gestures/ScrollableNestedScrollConnection;
+Landroidx/compose/foundation/gestures/ScrollableNode;
+Landroidx/compose/foundation/gestures/ScrollableState;
+Landroidx/compose/foundation/gestures/ScrollingLogic;
+Landroidx/compose/foundation/gestures/UpdatableAnimationState$animateToZero$1;
+Landroidx/compose/foundation/gestures/UpdatableAnimationState$animateToZero$4;
+Landroidx/compose/foundation/gestures/UpdatableAnimationState;
+Landroidx/compose/foundation/interaction/FocusInteraction$Focus;
+Landroidx/compose/foundation/interaction/FocusInteraction$Unfocus;
+Landroidx/compose/foundation/interaction/FocusInteractionKt$collectIsFocusedAsState$1$1$1;
+Landroidx/compose/foundation/interaction/FocusInteractionKt$collectIsFocusedAsState$1$1;
+Landroidx/compose/foundation/interaction/Interaction;
+Landroidx/compose/foundation/interaction/InteractionSource;
+Landroidx/compose/foundation/interaction/MutableInteractionSourceImpl;
+Landroidx/compose/foundation/interaction/PressInteraction$Press;
+Landroidx/compose/foundation/interaction/PressInteraction$Release;
+Landroidx/compose/foundation/interaction/PressInteractionKt$collectIsPressedAsState$1$1;
+Landroidx/compose/foundation/layout/Arrangement$Center$1;
+Landroidx/compose/foundation/layout/Arrangement$End$1;
+Landroidx/compose/foundation/layout/Arrangement$Horizontal;
+Landroidx/compose/foundation/layout/Arrangement$SpacedAligned;
+Landroidx/compose/foundation/layout/Arrangement$Top$1;
+Landroidx/compose/foundation/layout/Arrangement$Vertical;
+Landroidx/compose/foundation/layout/Arrangement;
+Landroidx/compose/foundation/layout/BoxKt$Box$2;
+Landroidx/compose/foundation/layout/BoxKt$EmptyBoxMeasurePolicy$1;
+Landroidx/compose/foundation/layout/BoxKt$boxMeasurePolicy$1$2;
+Landroidx/compose/foundation/layout/BoxKt$boxMeasurePolicy$1;
+Landroidx/compose/foundation/layout/BoxKt;
+Landroidx/compose/foundation/layout/BoxScope;
+Landroidx/compose/foundation/layout/BoxScopeInstance;
+Landroidx/compose/foundation/layout/ColumnKt;
+Landroidx/compose/foundation/layout/CrossAxisAlignment$VerticalCrossAxisAlignment;
+Landroidx/compose/foundation/layout/FillElement;
+Landroidx/compose/foundation/layout/FillNode;
+Landroidx/compose/foundation/layout/HorizontalAlignElement;
+Landroidx/compose/foundation/layout/HorizontalAlignNode;
+Landroidx/compose/foundation/layout/OffsetElement;
+Landroidx/compose/foundation/layout/OffsetKt;
+Landroidx/compose/foundation/layout/OffsetNode$measure$1;
+Landroidx/compose/foundation/layout/OffsetNode;
+Landroidx/compose/foundation/layout/PaddingElement;
+Landroidx/compose/foundation/layout/PaddingNode;
+Landroidx/compose/foundation/layout/PaddingValuesImpl;
+Landroidx/compose/foundation/layout/RowColumnImplKt$rowColumnMeasurePolicy$1;
+Landroidx/compose/foundation/layout/RowColumnMeasureHelperResult;
+Landroidx/compose/foundation/layout/RowColumnMeasurementHelper;
+Landroidx/compose/foundation/layout/RowColumnParentData;
+Landroidx/compose/foundation/layout/RowKt$DefaultRowMeasurePolicy$1;
+Landroidx/compose/foundation/layout/RowKt$rowMeasurePolicy$1$1;
+Landroidx/compose/foundation/layout/RowKt;
+Landroidx/compose/foundation/layout/RowScope;
+Landroidx/compose/foundation/layout/RowScopeInstance;
+Landroidx/compose/foundation/layout/SizeElement;
+Landroidx/compose/foundation/layout/SizeKt;
+Landroidx/compose/foundation/layout/SizeNode;
+Landroidx/compose/foundation/layout/SpacerMeasurePolicy;
+Landroidx/compose/foundation/layout/WrapContentElement;
+Landroidx/compose/foundation/layout/WrapContentNode$measure$1;
+Landroidx/compose/foundation/layout/WrapContentNode;
+Landroidx/compose/foundation/lazy/layout/DefaultLazyKey;
+Landroidx/compose/foundation/lazy/layout/IntervalList$Interval;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutIntervalContent$Interval;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory$CachedItemContent;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutItemProvider;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutItemReusePolicy;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3$2$1;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3$itemContentFactory$1$1;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutKt$LazyLayout$3;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutMeasureScopeImpl;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutPinnedItemList;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState$PrefetchHandle;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState$Prefetcher;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetchState;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher$PrefetchRequest;
+Landroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher;
+Landroidx/compose/foundation/lazy/layout/LazySaveableStateHolder$1;
+Landroidx/compose/foundation/lazy/layout/LazySaveableStateHolder$SaveableStateProvider$2$invoke$$inlined$onDispose$1;
+Landroidx/compose/foundation/lazy/layout/LazySaveableStateHolder;
+Landroidx/compose/foundation/lazy/layout/MutableIntervalList;
+Landroidx/compose/foundation/relocation/BringIntoViewChildNode;
+Landroidx/compose/foundation/relocation/BringIntoViewKt;
+Landroidx/compose/foundation/relocation/BringIntoViewParent;
+Landroidx/compose/foundation/relocation/BringIntoViewRequesterImpl$bringIntoView$1;
+Landroidx/compose/foundation/relocation/BringIntoViewRequesterImpl;
+Landroidx/compose/foundation/relocation/BringIntoViewRequesterNode;
+Landroidx/compose/foundation/relocation/BringIntoViewResponder;
+Landroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$1$1;
+Landroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$1;
+Landroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2$2;
+Landroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$2;
+Landroidx/compose/foundation/relocation/BringIntoViewResponderNode$bringChildIntoView$parentRect$1;
+Landroidx/compose/foundation/relocation/BringIntoViewResponderNode;
+Landroidx/compose/foundation/relocation/BringIntoViewResponder_androidKt$defaultBringIntoViewParent$1;
+Landroidx/compose/foundation/shape/CornerBasedShape;
+Landroidx/compose/foundation/shape/CornerSize;
+Landroidx/compose/foundation/shape/DpCornerSize;
+Landroidx/compose/foundation/shape/GenericShape;
+Landroidx/compose/foundation/shape/PercentCornerSize;
+Landroidx/compose/foundation/shape/RoundedCornerShape;
+Landroidx/compose/foundation/shape/RoundedCornerShapeKt;
+Landroidx/compose/foundation/text/EmptyMeasurePolicy;
+Landroidx/compose/foundation/text/modifiers/InlineDensity;
+Landroidx/compose/foundation/text/modifiers/MinLinesConstrainer;
+Landroidx/compose/foundation/text/modifiers/MultiParagraphLayoutCache;
+Landroidx/compose/foundation/text/modifiers/TextAnnotatedStringElement;
+Landroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode$TextSubstitutionValue;
+Landroidx/compose/foundation/text/modifiers/TextAnnotatedStringNode;
+Landroidx/compose/foundation/text/selection/SelectionRegistrarKt;
+Landroidx/compose/foundation/text/selection/TextSelectionColors;
+Landroidx/compose/foundation/text/selection/TextSelectionColorsKt;
+Landroidx/compose/material/ripple/AndroidRippleIndicationInstance;
+Landroidx/compose/material/ripple/PlatformRipple;
+Landroidx/compose/material/ripple/RippleAlpha;
+Landroidx/compose/material/ripple/RippleIndicationInstance;
+Landroidx/compose/material/ripple/RippleKt;
+Landroidx/compose/material/ripple/RippleTheme;
+Landroidx/compose/material/ripple/RippleThemeKt;
+Landroidx/compose/material3/ColorScheme;
+Landroidx/compose/material3/ColorSchemeKt;
+Landroidx/compose/material3/MaterialRippleTheme;
+Landroidx/compose/material3/MaterialThemeKt$MaterialTheme$2;
+Landroidx/compose/material3/ShapeDefaults;
+Landroidx/compose/material3/Shapes;
+Landroidx/compose/material3/ShapesKt$LocalShapes$1;
+Landroidx/compose/material3/ShapesKt;
+Landroidx/compose/material3/TextKt$ProvideTextStyle$1;
+Landroidx/compose/material3/TextKt$Text$1;
+Landroidx/compose/material3/TextKt;
+Landroidx/compose/material3/Typography;
+Landroidx/compose/material3/TypographyKt;
+Landroidx/compose/material3/tokens/ColorDarkTokens;
+Landroidx/compose/material3/tokens/PaletteTokens;
+Landroidx/compose/material3/tokens/ShapeTokens;
+Landroidx/compose/material3/tokens/TypeScaleTokens;
+Landroidx/compose/material3/tokens/TypefaceTokens;
+Landroidx/compose/material3/tokens/TypographyTokens;
+Landroidx/compose/runtime/Anchor;
+Landroidx/compose/runtime/Applier;
+Landroidx/compose/runtime/BroadcastFrameClock$FrameAwaiter;
+Landroidx/compose/runtime/BroadcastFrameClock;
+Landroidx/compose/runtime/ComposableSingletons$CompositionKt;
+Landroidx/compose/runtime/ComposeNodeLifecycleCallback;
+Landroidx/compose/runtime/Composer;
+Landroidx/compose/runtime/ComposerImpl$CompositionContextHolder;
+Landroidx/compose/runtime/ComposerImpl$CompositionContextImpl;
+Landroidx/compose/runtime/ComposerImpl$derivedStateObserver$1;
+Landroidx/compose/runtime/ComposerImpl;
+Landroidx/compose/runtime/Composition;
+Landroidx/compose/runtime/CompositionContext;
+Landroidx/compose/runtime/CompositionContextKt;
+Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;
+Landroidx/compose/runtime/CompositionImpl;
+Landroidx/compose/runtime/CompositionKt;
+Landroidx/compose/runtime/CompositionLocal;
+Landroidx/compose/runtime/CompositionLocalMap$Companion;
+Landroidx/compose/runtime/CompositionLocalMap;
+Landroidx/compose/runtime/CompositionObserverHolder;
+Landroidx/compose/runtime/CompositionScopedCoroutineScopeCanceller;
+Landroidx/compose/runtime/DerivedSnapshotState$ResultRecord;
+Landroidx/compose/runtime/DerivedSnapshotState;
+Landroidx/compose/runtime/DerivedStateObserver;
+Landroidx/compose/runtime/DisposableEffectImpl;
+Landroidx/compose/runtime/DisposableEffectResult;
+Landroidx/compose/runtime/DisposableEffectScope;
+Landroidx/compose/runtime/DynamicProvidableCompositionLocal;
+Landroidx/compose/runtime/GroupInfo;
+Landroidx/compose/runtime/IntStack;
+Landroidx/compose/runtime/Invalidation;
+Landroidx/compose/runtime/JoinedKey;
+Landroidx/compose/runtime/KeyInfo;
+Landroidx/compose/runtime/Latch$await$2$2;
+Landroidx/compose/runtime/Latch;
+Landroidx/compose/runtime/LaunchedEffectImpl;
+Landroidx/compose/runtime/LazyValueHolder;
+Landroidx/compose/runtime/MonotonicFrameClock;
+Landroidx/compose/runtime/MovableContentStateReference;
+Landroidx/compose/runtime/MutableFloatState;
+Landroidx/compose/runtime/MutableIntState;
+Landroidx/compose/runtime/MutableState;
+Landroidx/compose/runtime/OpaqueKey;
+Landroidx/compose/runtime/ParcelableSnapshotMutableFloatState;
+Landroidx/compose/runtime/ParcelableSnapshotMutableIntState;
+Landroidx/compose/runtime/ParcelableSnapshotMutableState$Companion$CREATOR$1;
+Landroidx/compose/runtime/ParcelableSnapshotMutableState;
+Landroidx/compose/runtime/PausableMonotonicFrameClock$withFrameNanos$1;
+Landroidx/compose/runtime/PausableMonotonicFrameClock;
+Landroidx/compose/runtime/Pending$keyMap$2;
+Landroidx/compose/runtime/Pending;
+Landroidx/compose/runtime/PersistentCompositionLocalMap;
+Landroidx/compose/runtime/ProduceStateScopeImpl;
+Landroidx/compose/runtime/ProvidableCompositionLocal;
+Landroidx/compose/runtime/ProvidedValue;
+Landroidx/compose/runtime/RecomposeScope;
+Landroidx/compose/runtime/RecomposeScopeImpl$end$1$2;
+Landroidx/compose/runtime/RecomposeScopeImpl;
+Landroidx/compose/runtime/RecomposeScopeOwner;
+Landroidx/compose/runtime/Recomposer$State;
+Landroidx/compose/runtime/Recomposer$effectJob$1$1;
+Landroidx/compose/runtime/Recomposer$join$2;
+Landroidx/compose/runtime/Recomposer$performRecompose$1$1;
+Landroidx/compose/runtime/Recomposer$recompositionRunner$2$3;
+Landroidx/compose/runtime/Recomposer$recompositionRunner$2$unregisterApplyObserver$1;
+Landroidx/compose/runtime/Recomposer$recompositionRunner$2;
+Landroidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2$1;
+Landroidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2;
+Landroidx/compose/runtime/Recomposer;
+Landroidx/compose/runtime/ReferentialEqualityPolicy;
+Landroidx/compose/runtime/RememberObserver;
+Landroidx/compose/runtime/SkippableUpdater;
+Landroidx/compose/runtime/SlotReader;
+Landroidx/compose/runtime/SlotTable;
+Landroidx/compose/runtime/SlotWriter;
+Landroidx/compose/runtime/SnapshotMutableFloatStateImpl$FloatStateStateRecord;
+Landroidx/compose/runtime/SnapshotMutableFloatStateImpl;
+Landroidx/compose/runtime/SnapshotMutableIntStateImpl$IntStateStateRecord;
+Landroidx/compose/runtime/SnapshotMutableIntStateImpl;
+Landroidx/compose/runtime/SnapshotMutableStateImpl$StateStateRecord;
+Landroidx/compose/runtime/SnapshotMutableStateImpl;
+Landroidx/compose/runtime/SnapshotMutationPolicy;
+Landroidx/compose/runtime/SnapshotStateKt__DerivedStateKt;
+Landroidx/compose/runtime/SnapshotStateKt__ProduceStateKt$produceState$3;
+Landroidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$collectAsState$1$1;
+Landroidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$collectAsState$1;
+Landroidx/compose/runtime/Stack;
+Landroidx/compose/runtime/State;
+Landroidx/compose/runtime/StaticProvidableCompositionLocal;
+Landroidx/compose/runtime/StaticValueHolder;
+Landroidx/compose/runtime/StructuralEqualityPolicy;
+Landroidx/compose/runtime/WeakReference;
+Landroidx/compose/runtime/changelist/ChangeList;
+Landroidx/compose/runtime/changelist/ComposerChangeListWriter;
+Landroidx/compose/runtime/changelist/FixupList;
+Landroidx/compose/runtime/changelist/Operation$AdvanceSlotsBy;
+Landroidx/compose/runtime/changelist/Operation$DeactivateCurrentGroup;
+Landroidx/compose/runtime/changelist/Operation$Downs;
+Landroidx/compose/runtime/changelist/Operation$EndCompositionScope;
+Landroidx/compose/runtime/changelist/Operation$EndCurrentGroup;
+Landroidx/compose/runtime/changelist/Operation$EnsureGroupStarted;
+Landroidx/compose/runtime/changelist/Operation$EnsureRootGroupStarted;
+Landroidx/compose/runtime/changelist/Operation$InsertNodeFixup;
+Landroidx/compose/runtime/changelist/Operation$InsertSlots;
+Landroidx/compose/runtime/changelist/Operation$InsertSlotsWithFixups;
+Landroidx/compose/runtime/changelist/Operation$MoveCurrentGroup;
+Landroidx/compose/runtime/changelist/Operation$PostInsertNodeFixup;
+Landroidx/compose/runtime/changelist/Operation$Remember;
+Landroidx/compose/runtime/changelist/Operation$SideEffect;
+Landroidx/compose/runtime/changelist/Operation$UpdateAuxData;
+Landroidx/compose/runtime/changelist/Operation$UpdateNode;
+Landroidx/compose/runtime/changelist/Operation$UpdateValue;
+Landroidx/compose/runtime/changelist/Operation$Ups;
+Landroidx/compose/runtime/changelist/Operation$UseCurrentNode;
+Landroidx/compose/runtime/changelist/Operation;
+Landroidx/compose/runtime/changelist/Operations$OpIterator;
+Landroidx/compose/runtime/changelist/Operations;
+Landroidx/compose/runtime/collection/IdentityArrayIntMap;
+Landroidx/compose/runtime/collection/IdentityArrayMap$asMap$1$entries$1$iterator$1$1;
+Landroidx/compose/runtime/collection/IdentityArraySet;
+Landroidx/compose/runtime/collection/MutableVector$MutableVectorList;
+Landroidx/compose/runtime/collection/MutableVector$VectorListIterator;
+Landroidx/compose/runtime/collection/MutableVector;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/ImmutableList;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/ImmutableSet;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/PersistentList;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/PersistentMap$Builder;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/PersistentMap;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/PersistentSet;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/AbstractPersistentList;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableList/SmallPersistentVector;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMap;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBaseIterator;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapBuilder;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapKeys;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapKeysIterator;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNodeBaseIterator;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNodeKeysIterator;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/persistentOrderedSet/Links;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/persistentOrderedSet/PersistentOrderedSet;
+Landroidx/compose/runtime/external/kotlinx/collections/immutable/internal/DeltaCounter;
+Landroidx/compose/runtime/internal/ComposableLambda;
+Landroidx/compose/runtime/internal/ComposableLambdaImpl;
+Landroidx/compose/runtime/internal/PersistentCompositionLocalHashMap$Builder;
+Landroidx/compose/runtime/internal/PersistentCompositionLocalHashMap;
+Landroidx/compose/runtime/internal/ThreadMap;
+Landroidx/compose/runtime/saveable/ListSaverKt$listSaver$1;
+Landroidx/compose/runtime/saveable/RememberSaveableKt$rememberSaveable$1;
+Landroidx/compose/runtime/saveable/SaveableHolder;
+Landroidx/compose/runtime/saveable/SaveableStateHolder;
+Landroidx/compose/runtime/saveable/SaveableStateHolderImpl$RegistryHolder$registry$1;
+Landroidx/compose/runtime/saveable/SaveableStateHolderImpl$RegistryHolder;
+Landroidx/compose/runtime/saveable/SaveableStateHolderImpl$SaveableStateProvider$1$1$invoke$$inlined$onDispose$1;
+Landroidx/compose/runtime/saveable/SaveableStateHolderImpl;
+Landroidx/compose/runtime/saveable/SaveableStateRegistry;
+Landroidx/compose/runtime/saveable/SaveableStateRegistryImpl$registerProvider$3;
+Landroidx/compose/runtime/saveable/SaveableStateRegistryImpl;
+Landroidx/compose/runtime/saveable/SaveableStateRegistryKt;
+Landroidx/compose/runtime/saveable/SaverKt$Saver$1;
+Landroidx/compose/runtime/saveable/SaverKt;
+Landroidx/compose/runtime/snapshots/GlobalSnapshot$1$1$1;
+Landroidx/compose/runtime/snapshots/GlobalSnapshot;
+Landroidx/compose/runtime/snapshots/MutableSnapshot;
+Landroidx/compose/runtime/snapshots/ObserverHandle;
+Landroidx/compose/runtime/snapshots/ReadonlySnapshot;
+Landroidx/compose/runtime/snapshots/Snapshot$Companion$$ExternalSyntheticLambda0;
+Landroidx/compose/runtime/snapshots/Snapshot;
+Landroidx/compose/runtime/snapshots/SnapshotApplyResult$Failure;
+Landroidx/compose/runtime/snapshots/SnapshotApplyResult$Success;
+Landroidx/compose/runtime/snapshots/SnapshotDoubleIndexHeap;
+Landroidx/compose/runtime/snapshots/SnapshotIdSet;
+Landroidx/compose/runtime/snapshots/SnapshotKt$mergedReadObserver$1;
+Landroidx/compose/runtime/snapshots/SnapshotKt;
+Landroidx/compose/runtime/snapshots/SnapshotMutableState;
+Landroidx/compose/runtime/snapshots/SnapshotStateList$StateListStateRecord;
+Landroidx/compose/runtime/snapshots/SnapshotStateList$addAll$1;
+Landroidx/compose/runtime/snapshots/SnapshotStateList;
+Landroidx/compose/runtime/snapshots/SnapshotStateListKt;
+Landroidx/compose/runtime/snapshots/SnapshotStateObserver$ObservedScopeMap;
+Landroidx/compose/runtime/snapshots/SnapshotStateObserver;
+Landroidx/compose/runtime/snapshots/StateObject;
+Landroidx/compose/runtime/snapshots/StateRecord;
+Landroidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot;
+Landroidx/compose/runtime/tooling/InspectionTablesKt;
+Landroidx/compose/ui/Alignment$Horizontal;
+Landroidx/compose/ui/Alignment$Vertical;
+Landroidx/compose/ui/Alignment;
+Landroidx/compose/ui/BiasAlignment$Horizontal;
+Landroidx/compose/ui/BiasAlignment$Vertical;
+Landroidx/compose/ui/BiasAlignment;
+Landroidx/compose/ui/CombinedModifier$toString$1;
+Landroidx/compose/ui/CombinedModifier;
+Landroidx/compose/ui/ComposedModifier;
+Landroidx/compose/ui/CompositionLocalMapInjectionElement;
+Landroidx/compose/ui/Modifier$Companion;
+Landroidx/compose/ui/Modifier$Element;
+Landroidx/compose/ui/Modifier$Node;
+Landroidx/compose/ui/Modifier;
+Landroidx/compose/ui/MotionDurationScale;
+Landroidx/compose/ui/ZIndexElement;
+Landroidx/compose/ui/ZIndexNode$measure$1;
+Landroidx/compose/ui/ZIndexNode;
+Landroidx/compose/ui/autofill/AndroidAutofill;
+Landroidx/compose/ui/autofill/Autofill;
+Landroidx/compose/ui/autofill/AutofillCallback;
+Landroidx/compose/ui/autofill/AutofillTree;
+Landroidx/compose/ui/draw/BuildDrawCacheParams;
+Landroidx/compose/ui/draw/CacheDrawModifierNode;
+Landroidx/compose/ui/draw/CacheDrawModifierNodeImpl;
+Landroidx/compose/ui/draw/CacheDrawScope$onDrawBehind$1;
+Landroidx/compose/ui/draw/CacheDrawScope;
+Landroidx/compose/ui/draw/ClipKt;
+Landroidx/compose/ui/draw/DrawModifier;
+Landroidx/compose/ui/draw/DrawResult;
+Landroidx/compose/ui/draw/DrawWithCacheElement;
+Landroidx/compose/ui/draw/EmptyBuildDrawCacheParams;
+Landroidx/compose/ui/draw/PainterElement;
+Landroidx/compose/ui/draw/PainterNode$measure$1;
+Landroidx/compose/ui/draw/PainterNode;
+Landroidx/compose/ui/focus/FocusChangedElement;
+Landroidx/compose/ui/focus/FocusChangedNode;
+Landroidx/compose/ui/focus/FocusDirection;
+Landroidx/compose/ui/focus/FocusEventModifierNode;
+Landroidx/compose/ui/focus/FocusInvalidationManager;
+Landroidx/compose/ui/focus/FocusModifierKt;
+Landroidx/compose/ui/focus/FocusOwner;
+Landroidx/compose/ui/focus/FocusOwnerImpl$modifier$1;
+Landroidx/compose/ui/focus/FocusOwnerImpl$moveFocus$foundNextItem$1;
+Landroidx/compose/ui/focus/FocusOwnerImpl;
+Landroidx/compose/ui/focus/FocusProperties$exit$1;
+Landroidx/compose/ui/focus/FocusProperties;
+Landroidx/compose/ui/focus/FocusPropertiesImpl;
+Landroidx/compose/ui/focus/FocusPropertiesModifierNode;
+Landroidx/compose/ui/focus/FocusRequester;
+Landroidx/compose/ui/focus/FocusRequesterModifierNode;
+Landroidx/compose/ui/focus/FocusState;
+Landroidx/compose/ui/focus/FocusStateImpl;
+Landroidx/compose/ui/focus/FocusTargetNode$FocusTargetElement;
+Landroidx/compose/ui/focus/FocusTargetNode;
+Landroidx/compose/ui/geometry/CornerRadius;
+Landroidx/compose/ui/geometry/MutableRect;
+Landroidx/compose/ui/geometry/Offset;
+Landroidx/compose/ui/geometry/Rect;
+Landroidx/compose/ui/geometry/RoundRect;
+Landroidx/compose/ui/geometry/Size;
+Landroidx/compose/ui/graphics/AndroidCanvas;
+Landroidx/compose/ui/graphics/AndroidCanvas_androidKt;
+Landroidx/compose/ui/graphics/AndroidImageBitmap;
+Landroidx/compose/ui/graphics/AndroidPaint;
+Landroidx/compose/ui/graphics/AndroidPaint_androidKt$WhenMappings;
+Landroidx/compose/ui/graphics/AndroidPath;
+Landroidx/compose/ui/graphics/BlendModeColorFilter;
+Landroidx/compose/ui/graphics/BlendModeColorFilterHelper;
+Landroidx/compose/ui/graphics/BlockGraphicsLayerElement;
+Landroidx/compose/ui/graphics/BlockGraphicsLayerModifier;
+Landroidx/compose/ui/graphics/Brush;
+Landroidx/compose/ui/graphics/BrushKt$ShaderBrush$1;
+Landroidx/compose/ui/graphics/BrushKt;
+Landroidx/compose/ui/graphics/Canvas;
+Landroidx/compose/ui/graphics/CanvasZHelper$$ExternalSyntheticApiModelOutline0;
+Landroidx/compose/ui/graphics/Color;
+Landroidx/compose/ui/graphics/ColorSpaceVerificationHelper$$ExternalSyntheticLambda1;
+Landroidx/compose/ui/graphics/Float16;
+Landroidx/compose/ui/graphics/GraphicsLayerElement;
+Landroidx/compose/ui/graphics/GraphicsLayerScopeKt;
+Landroidx/compose/ui/graphics/ImageBitmap;
+Landroidx/compose/ui/graphics/ImageBitmapConfig;
+Landroidx/compose/ui/graphics/Matrix;
+Landroidx/compose/ui/graphics/Outline$Generic;
+Landroidx/compose/ui/graphics/Outline$Rectangle;
+Landroidx/compose/ui/graphics/Outline$Rounded;
+Landroidx/compose/ui/graphics/Path;
+Landroidx/compose/ui/graphics/RectangleShapeKt$RectangleShape$1;
+Landroidx/compose/ui/graphics/ReusableGraphicsLayerScope;
+Landroidx/compose/ui/graphics/Shadow;
+Landroidx/compose/ui/graphics/Shape;
+Landroidx/compose/ui/graphics/SimpleGraphicsLayerModifier$layerBlock$1;
+Landroidx/compose/ui/graphics/SimpleGraphicsLayerModifier;
+Landroidx/compose/ui/graphics/SolidColor;
+Landroidx/compose/ui/graphics/TransformOrigin;
+Landroidx/compose/ui/graphics/colorspace/Adaptation$Companion$Bradford$1;
+Landroidx/compose/ui/graphics/colorspace/Adaptation;
+Landroidx/compose/ui/graphics/colorspace/ColorModel;
+Landroidx/compose/ui/graphics/colorspace/ColorSpace;
+Landroidx/compose/ui/graphics/colorspace/ColorSpaces;
+Landroidx/compose/ui/graphics/colorspace/Connector$Companion$identity$1;
+Landroidx/compose/ui/graphics/colorspace/Connector;
+Landroidx/compose/ui/graphics/colorspace/DoubleFunction;
+Landroidx/compose/ui/graphics/colorspace/Lab;
+Landroidx/compose/ui/graphics/colorspace/Oklab;
+Landroidx/compose/ui/graphics/colorspace/Rgb$$ExternalSyntheticLambda0;
+Landroidx/compose/ui/graphics/colorspace/Rgb$$ExternalSyntheticLambda1;
+Landroidx/compose/ui/graphics/colorspace/Rgb$$ExternalSyntheticLambda2;
+Landroidx/compose/ui/graphics/colorspace/Rgb$eotf$1;
+Landroidx/compose/ui/graphics/colorspace/Rgb;
+Landroidx/compose/ui/graphics/colorspace/TransferParameters;
+Landroidx/compose/ui/graphics/colorspace/WhitePoint;
+Landroidx/compose/ui/graphics/colorspace/Xyz;
+Landroidx/compose/ui/graphics/drawscope/CanvasDrawScope$DrawParams;
+Landroidx/compose/ui/graphics/drawscope/CanvasDrawScope$drawContext$1;
+Landroidx/compose/ui/graphics/drawscope/CanvasDrawScope;
+Landroidx/compose/ui/graphics/drawscope/CanvasDrawScopeKt$asDrawTransform$1;
+Landroidx/compose/ui/graphics/drawscope/ContentDrawScope;
+Landroidx/compose/ui/graphics/drawscope/DrawScope;
+Landroidx/compose/ui/graphics/drawscope/EmptyCanvas;
+Landroidx/compose/ui/graphics/drawscope/Fill;
+Landroidx/compose/ui/graphics/drawscope/Stroke;
+Landroidx/compose/ui/graphics/painter/BitmapPainter;
+Landroidx/compose/ui/graphics/painter/Painter;
+Landroidx/compose/ui/graphics/vector/GroupComponent;
+Landroidx/compose/ui/graphics/vector/ImageVector$Builder$GroupParams;
+Landroidx/compose/ui/graphics/vector/ImageVector$Builder;
+Landroidx/compose/ui/graphics/vector/ImageVector;
+Landroidx/compose/ui/graphics/vector/VNode;
+Landroidx/compose/ui/graphics/vector/VectorComponent;
+Landroidx/compose/ui/graphics/vector/VectorGroup;
+Landroidx/compose/ui/graphics/vector/VectorKt;
+Landroidx/compose/ui/graphics/vector/VectorNode;
+Landroidx/compose/ui/graphics/vector/VectorPainter;
+Landroidx/compose/ui/graphics/vector/VectorPath;
+Landroidx/compose/ui/graphics/vector/compat/AndroidVectorParser;
+Landroidx/compose/ui/hapticfeedback/HapticFeedback;
+Landroidx/compose/ui/input/InputMode;
+Landroidx/compose/ui/input/InputModeManager;
+Landroidx/compose/ui/input/InputModeManagerImpl;
+Landroidx/compose/ui/input/key/Key;
+Landroidx/compose/ui/input/key/KeyEvent;
+Landroidx/compose/ui/input/key/KeyInputElement;
+Landroidx/compose/ui/input/key/KeyInputModifierNode;
+Landroidx/compose/ui/input/key/KeyInputNode;
+Landroidx/compose/ui/input/key/Key_androidKt;
+Landroidx/compose/ui/input/nestedscroll/NestedScrollConnection;
+Landroidx/compose/ui/input/nestedscroll/NestedScrollDispatcher;
+Landroidx/compose/ui/input/nestedscroll/NestedScrollNode;
+Landroidx/compose/ui/input/nestedscroll/NestedScrollNodeKt;
+Landroidx/compose/ui/input/pointer/AndroidPointerIconType;
+Landroidx/compose/ui/input/pointer/MotionEventAdapter;
+Landroidx/compose/ui/input/pointer/Node;
+Landroidx/compose/ui/input/pointer/NodeParent;
+Landroidx/compose/ui/input/pointer/PointerEvent;
+Landroidx/compose/ui/input/pointer/PointerIcon;
+Landroidx/compose/ui/input/pointer/PointerIconService;
+Landroidx/compose/ui/input/pointer/PointerInputChange;
+Landroidx/compose/ui/input/pointer/PointerInputScope;
+Landroidx/compose/ui/input/pointer/PointerKeyboardModifiers;
+Landroidx/compose/ui/input/pointer/PositionCalculator;
+Landroidx/compose/ui/input/pointer/SuspendPointerInputElement;
+Landroidx/compose/ui/input/pointer/SuspendingPointerInputFilterKt;
+Landroidx/compose/ui/input/pointer/SuspendingPointerInputModifierNode;
+Landroidx/compose/ui/input/pointer/SuspendingPointerInputModifierNodeImpl$PointerEventHandlerCoroutine;
+Landroidx/compose/ui/input/pointer/SuspendingPointerInputModifierNodeImpl;
+Landroidx/compose/ui/input/pointer/util/DataPointAtTime;
+Landroidx/compose/ui/input/pointer/util/PointerIdArray;
+Landroidx/compose/ui/input/pointer/util/VelocityTracker1D;
+Landroidx/compose/ui/input/pointer/util/VelocityTracker;
+Landroidx/compose/ui/input/rotary/RotaryInputElement;
+Landroidx/compose/ui/input/rotary/RotaryInputModifierKt;
+Landroidx/compose/ui/input/rotary/RotaryInputModifierNode;
+Landroidx/compose/ui/input/rotary/RotaryInputNode;
+Landroidx/compose/ui/layout/AlignmentLine;
+Landroidx/compose/ui/layout/AlignmentLineKt$FirstBaseline$1;
+Landroidx/compose/ui/layout/AlignmentLineKt$LastBaseline$1;
+Landroidx/compose/ui/layout/AlignmentLineKt;
+Landroidx/compose/ui/layout/BeyondBoundsLayout$BeyondBoundsScope;
+Landroidx/compose/ui/layout/BeyondBoundsLayout;
+Landroidx/compose/ui/layout/BeyondBoundsLayoutKt;
+Landroidx/compose/ui/layout/ComposableSingletons$SubcomposeLayoutKt;
+Landroidx/compose/ui/layout/ContentScale;
+Landroidx/compose/ui/layout/DefaultIntrinsicMeasurable;
+Landroidx/compose/ui/layout/FixedSizeIntrinsicsPlaceable;
+Landroidx/compose/ui/layout/HorizontalAlignmentLine;
+Landroidx/compose/ui/layout/IntrinsicMeasureScope;
+Landroidx/compose/ui/layout/IntrinsicMinMax;
+Landroidx/compose/ui/layout/IntrinsicWidthHeight;
+Landroidx/compose/ui/layout/IntrinsicsMeasureScope;
+Landroidx/compose/ui/layout/LayoutCoordinates;
+Landroidx/compose/ui/layout/LayoutElement;
+Landroidx/compose/ui/layout/LayoutKt$materializerOf$1;
+Landroidx/compose/ui/layout/LayoutKt;
+Landroidx/compose/ui/layout/LayoutModifierImpl;
+Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState$NodeState;
+Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState$PostLookaheadMeasureScopeImpl;
+Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState$Scope;
+Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1$measure-3p2s80s$$inlined$createMeasureResult$1;
+Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState$createMeasurePolicy$1;
+Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState$precompose$1;
+Landroidx/compose/ui/layout/LayoutNodeSubcompositionsState;
+Landroidx/compose/ui/layout/LookaheadLayoutCoordinates;
+Landroidx/compose/ui/layout/Measurable;
+Landroidx/compose/ui/layout/MeasurePolicy;
+Landroidx/compose/ui/layout/MeasureResult;
+Landroidx/compose/ui/layout/MeasureScope$layout$1;
+Landroidx/compose/ui/layout/MeasureScope;
+Landroidx/compose/ui/layout/Measured;
+Landroidx/compose/ui/layout/OnRemeasuredModifier;
+Landroidx/compose/ui/layout/OnSizeChangedModifier;
+Landroidx/compose/ui/layout/PinnableContainerKt;
+Landroidx/compose/ui/layout/Placeable$PlacementScope$Companion;
+Landroidx/compose/ui/layout/Placeable$PlacementScope;
+Landroidx/compose/ui/layout/Placeable;
+Landroidx/compose/ui/layout/PlaceableKt;
+Landroidx/compose/ui/layout/Remeasurement;
+Landroidx/compose/ui/layout/RemeasurementModifier;
+Landroidx/compose/ui/layout/RootMeasurePolicy$measure$2;
+Landroidx/compose/ui/layout/RootMeasurePolicy;
+Landroidx/compose/ui/layout/ScaleFactor;
+Landroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$2;
+Landroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$4;
+Landroidx/compose/ui/layout/SubcomposeLayoutKt$SubcomposeLayout$5$1$invoke$$inlined$onDispose$1;
+Landroidx/compose/ui/layout/SubcomposeLayoutState$setRoot$1;
+Landroidx/compose/ui/layout/SubcomposeLayoutState;
+Landroidx/compose/ui/layout/SubcomposeMeasureScope;
+Landroidx/compose/ui/layout/SubcomposeSlotReusePolicy$SlotIdsSet;
+Landroidx/compose/ui/layout/SubcomposeSlotReusePolicy;
+Landroidx/compose/ui/modifier/BackwardsCompatLocalMap;
+Landroidx/compose/ui/modifier/EmptyMap;
+Landroidx/compose/ui/modifier/ModifierLocal;
+Landroidx/compose/ui/modifier/ModifierLocalConsumer;
+Landroidx/compose/ui/modifier/ModifierLocalManager;
+Landroidx/compose/ui/modifier/ModifierLocalModifierNode;
+Landroidx/compose/ui/modifier/ModifierLocalProvider;
+Landroidx/compose/ui/modifier/ModifierLocalReadScope;
+Landroidx/compose/ui/modifier/ProvidableModifierLocal;
+Landroidx/compose/ui/modifier/SingleLocalMap;
+Landroidx/compose/ui/node/AlignmentLines;
+Landroidx/compose/ui/node/AlignmentLinesOwner;
+Landroidx/compose/ui/node/BackwardsCompatNode;
+Landroidx/compose/ui/node/CanFocusChecker;
+Landroidx/compose/ui/node/ComposeUiNode$Companion;
+Landroidx/compose/ui/node/ComposeUiNode;
+Landroidx/compose/ui/node/CompositionLocalConsumerModifierNode;
+Landroidx/compose/ui/node/DelegatableNode;
+Landroidx/compose/ui/node/DelegatingNode;
+Landroidx/compose/ui/node/DrawModifierNode;
+Landroidx/compose/ui/node/GlobalPositionAwareModifierNode;
+Landroidx/compose/ui/node/HitTestResult;
+Landroidx/compose/ui/node/InnerNodeCoordinator;
+Landroidx/compose/ui/node/IntrinsicsPolicy;
+Landroidx/compose/ui/node/LayerPositionalProperties;
+Landroidx/compose/ui/node/LayoutAwareModifierNode;
+Landroidx/compose/ui/node/LayoutModifierNode;
+Landroidx/compose/ui/node/LayoutModifierNodeCoordinator;
+Landroidx/compose/ui/node/LayoutNode$$ExternalSyntheticLambda0;
+Landroidx/compose/ui/node/LayoutNode$Companion$DummyViewConfiguration$1;
+Landroidx/compose/ui/node/LayoutNode$Companion$ErrorMeasurePolicy$1;
+Landroidx/compose/ui/node/LayoutNode$NoIntrinsicsMeasurePolicy;
+Landroidx/compose/ui/node/LayoutNode$WhenMappings;
+Landroidx/compose/ui/node/LayoutNode$_foldedChildren$1;
+Landroidx/compose/ui/node/LayoutNode;
+Landroidx/compose/ui/node/LayoutNodeDrawScope;
+Landroidx/compose/ui/node/LayoutNodeLayoutDelegate$LookaheadPassDelegate;
+Landroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate$placeOuterCoordinator$1;
+Landroidx/compose/ui/node/LayoutNodeLayoutDelegate$MeasurePassDelegate;
+Landroidx/compose/ui/node/LayoutNodeLayoutDelegate$performMeasure$2;
+Landroidx/compose/ui/node/LayoutNodeLayoutDelegate;
+Landroidx/compose/ui/node/LookaheadAlignmentLines;
+Landroidx/compose/ui/node/LookaheadCapablePlaceable;
+Landroidx/compose/ui/node/LookaheadDelegate;
+Landroidx/compose/ui/node/MeasureAndLayoutDelegate$PostponedRequest;
+Landroidx/compose/ui/node/MeasureAndLayoutDelegate;
+Landroidx/compose/ui/node/ModifierNodeElement;
+Landroidx/compose/ui/node/NodeChain$Differ;
+Landroidx/compose/ui/node/NodeChain;
+Landroidx/compose/ui/node/NodeChainKt$SentinelHead$1;
+Landroidx/compose/ui/node/NodeChainKt;
+Landroidx/compose/ui/node/NodeCoordinator$HitTestSource;
+Landroidx/compose/ui/node/NodeCoordinator$invoke$1;
+Landroidx/compose/ui/node/NodeCoordinator;
+Landroidx/compose/ui/node/NodeMeasuringIntrinsics$IntrinsicMinMax;
+Landroidx/compose/ui/node/NodeMeasuringIntrinsics$IntrinsicWidthHeight;
+Landroidx/compose/ui/node/ObserverModifierNode;
+Landroidx/compose/ui/node/ObserverNodeOwnerScope;
+Landroidx/compose/ui/node/OnPositionedDispatcher$Companion$DepthComparator;
+Landroidx/compose/ui/node/OnPositionedDispatcher;
+Landroidx/compose/ui/node/OwnedLayer;
+Landroidx/compose/ui/node/Owner$OnLayoutCompletedListener;
+Landroidx/compose/ui/node/Owner;
+Landroidx/compose/ui/node/OwnerScope;
+Landroidx/compose/ui/node/OwnerSnapshotObserver;
+Landroidx/compose/ui/node/ParentDataModifierNode;
+Landroidx/compose/ui/node/PointerInputModifierNode;
+Landroidx/compose/ui/node/RootForTest;
+Landroidx/compose/ui/node/SemanticsModifierNode;
+Landroidx/compose/ui/node/TailModifierNode;
+Landroidx/compose/ui/node/TreeSet;
+Landroidx/compose/ui/node/UiApplier;
+Landroidx/compose/ui/platform/AbstractComposeView;
+Landroidx/compose/ui/platform/AccessibilityManager;
+Landroidx/compose/ui/platform/AndroidAccessibilityManager;
+Landroidx/compose/ui/platform/AndroidClipboardManager;
+Landroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticApiModelOutline0;
+Landroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticLambda1;
+Landroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticLambda2;
+Landroidx/compose/ui/platform/AndroidComposeView$$ExternalSyntheticLambda3;
+Landroidx/compose/ui/platform/AndroidComposeView$AndroidComposeViewTranslationCallback;
+Landroidx/compose/ui/platform/AndroidComposeView$ViewTreeOwners;
+Landroidx/compose/ui/platform/AndroidComposeView$focusOwner$1;
+Landroidx/compose/ui/platform/AndroidComposeView$pointerIconService$1;
+Landroidx/compose/ui/platform/AndroidComposeView$viewTreeOwners$2;
+Landroidx/compose/ui/platform/AndroidComposeView;
+Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$$ExternalSyntheticLambda1;
+Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$$ExternalSyntheticLambda2;
+Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$1;
+Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$MyNodeProvider;
+Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$SemanticsNodeCopy;
+Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$boundsUpdatesEventLoop$1;
+Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$sendScrollEventIfNeeded$1;
+Landroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;
+Landroidx/compose/ui/platform/AndroidComposeViewForceDarkModeQ;
+Landroidx/compose/ui/platform/AndroidComposeViewTranslationCallbackS;
+Landroidx/compose/ui/platform/AndroidComposeViewVerificationHelperMethodsN;
+Landroidx/compose/ui/platform/AndroidComposeViewVerificationHelperMethodsO;
+Landroidx/compose/ui/platform/AndroidCompositionLocals_androidKt$obtainImageVectorCache$callbacks$1$1;
+Landroidx/compose/ui/platform/AndroidCompositionLocals_androidKt;
+Landroidx/compose/ui/platform/AndroidUiDispatcher$dispatchCallback$1;
+Landroidx/compose/ui/platform/AndroidUiDispatcher;
+Landroidx/compose/ui/platform/AndroidUiFrameClock$withFrameNanos$2$callback$1;
+Landroidx/compose/ui/platform/AndroidUiFrameClock;
+Landroidx/compose/ui/platform/AndroidUriHandler;
+Landroidx/compose/ui/platform/AndroidViewConfiguration;
+Landroidx/compose/ui/platform/CalculateMatrixToWindow;
+Landroidx/compose/ui/platform/CalculateMatrixToWindowApi29;
+Landroidx/compose/ui/platform/ClipboardManager;
+Landroidx/compose/ui/platform/ComposableSingletons$Wrapper_androidKt;
+Landroidx/compose/ui/platform/ComposeView;
+Landroidx/compose/ui/platform/CompositionLocalsKt;
+Landroidx/compose/ui/platform/DeviceRenderNode;
+Landroidx/compose/ui/platform/DisposableSaveableStateRegistry;
+Landroidx/compose/ui/platform/DisposableSaveableStateRegistry_androidKt$DisposableSaveableStateRegistry$1;
+Landroidx/compose/ui/platform/DrawChildContainer;
+Landroidx/compose/ui/platform/GlobalSnapshotManager$ensureStarted$1;
+Landroidx/compose/ui/platform/GlobalSnapshotManager;
+Landroidx/compose/ui/platform/InspectableModifier$End;
+Landroidx/compose/ui/platform/InspectableModifier;
+Landroidx/compose/ui/platform/LayerMatrixCache;
+Landroidx/compose/ui/platform/MotionDurationScaleImpl;
+Landroidx/compose/ui/platform/OutlineResolver;
+Landroidx/compose/ui/platform/RenderNodeApi29;
+Landroidx/compose/ui/platform/RenderNodeApi29VerificationHelper;
+Landroidx/compose/ui/platform/RenderNodeLayer;
+Landroidx/compose/ui/platform/ScrollObservationScope;
+Landroidx/compose/ui/platform/SoftwareKeyboardController;
+Landroidx/compose/ui/platform/TextToolbar;
+Landroidx/compose/ui/platform/UriHandler;
+Landroidx/compose/ui/platform/ViewCompositionStrategy;
+Landroidx/compose/ui/platform/ViewConfiguration;
+Landroidx/compose/ui/platform/ViewLayer$Companion$OutlineProvider$1;
+Landroidx/compose/ui/platform/ViewLayer;
+Landroidx/compose/ui/platform/ViewLayerContainer;
+Landroidx/compose/ui/platform/WeakCache;
+Landroidx/compose/ui/platform/WindowInfo;
+Landroidx/compose/ui/platform/WindowInfoImpl;
+Landroidx/compose/ui/platform/WindowRecomposerFactory$Companion$$ExternalSyntheticLambda0;
+Landroidx/compose/ui/platform/WindowRecomposerFactory;
+Landroidx/compose/ui/platform/WindowRecomposerPolicy$createAndInstallWindowRecomposer$unsetJob$1;
+Landroidx/compose/ui/platform/WindowRecomposerPolicy;
+Landroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$1;
+Landroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$WhenMappings;
+Landroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1$1$1;
+Landroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2$onStateChanged$1;
+Landroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$2;
+Landroidx/compose/ui/platform/WindowRecomposer_androidKt$getAnimationScaleFlowFor$1$1$1;
+Landroidx/compose/ui/platform/WindowRecomposer_androidKt;
+Landroidx/compose/ui/platform/WrappedComposition$setContent$1$1$1;
+Landroidx/compose/ui/platform/WrappedComposition$setContent$1$1;
+Landroidx/compose/ui/platform/WrappedComposition$setContent$1;
+Landroidx/compose/ui/platform/WrappedComposition;
+Landroidx/compose/ui/platform/WrapperRenderNodeLayerHelperMethods;
+Landroidx/compose/ui/platform/WrapperVerificationHelperMethods;
+Landroidx/compose/ui/platform/Wrapper_androidKt;
+Landroidx/compose/ui/res/ImageVectorCache$ImageVectorEntry;
+Landroidx/compose/ui/res/ImageVectorCache$Key;
+Landroidx/compose/ui/res/ImageVectorCache;
+Landroidx/compose/ui/semantics/AppendedSemanticsElement;
+Landroidx/compose/ui/semantics/CollectionInfo;
+Landroidx/compose/ui/semantics/CoreSemanticsModifierNode;
+Landroidx/compose/ui/semantics/EmptySemanticsElement;
+Landroidx/compose/ui/semantics/EmptySemanticsModifier;
+Landroidx/compose/ui/semantics/Role;
+Landroidx/compose/ui/semantics/ScrollAxisRange;
+Landroidx/compose/ui/semantics/SemanticsConfiguration;
+Landroidx/compose/ui/semantics/SemanticsModifier;
+Landroidx/compose/ui/semantics/SemanticsModifierKt;
+Landroidx/compose/ui/semantics/SemanticsNode;
+Landroidx/compose/ui/semantics/SemanticsOwner;
+Landroidx/compose/ui/semantics/SemanticsProperties;
+Landroidx/compose/ui/semantics/SemanticsPropertiesKt;
+Landroidx/compose/ui/semantics/SemanticsPropertyKey;
+Landroidx/compose/ui/semantics/SemanticsPropertyReceiver;
+Landroidx/compose/ui/text/AndroidParagraph;
+Landroidx/compose/ui/text/AnnotatedString$Range;
+Landroidx/compose/ui/text/AnnotatedString;
+Landroidx/compose/ui/text/AnnotatedStringKt;
+Landroidx/compose/ui/text/EmojiSupportMatch;
+Landroidx/compose/ui/text/MultiParagraph;
+Landroidx/compose/ui/text/MultiParagraphIntrinsics$maxIntrinsicWidth$2;
+Landroidx/compose/ui/text/MultiParagraphIntrinsics;
+Landroidx/compose/ui/text/ParagraphInfo;
+Landroidx/compose/ui/text/ParagraphIntrinsicInfo;
+Landroidx/compose/ui/text/ParagraphIntrinsics;
+Landroidx/compose/ui/text/ParagraphStyle;
+Landroidx/compose/ui/text/ParagraphStyleKt;
+Landroidx/compose/ui/text/PlatformParagraphStyle;
+Landroidx/compose/ui/text/PlatformTextStyle;
+Landroidx/compose/ui/text/SaversKt$ColorSaver$1;
+Landroidx/compose/ui/text/SaversKt$ColorSaver$2;
+Landroidx/compose/ui/text/SaversKt;
+Landroidx/compose/ui/text/SpanStyle;
+Landroidx/compose/ui/text/SpanStyleKt;
+Landroidx/compose/ui/text/TextLayoutInput;
+Landroidx/compose/ui/text/TextLayoutResult;
+Landroidx/compose/ui/text/TextRange;
+Landroidx/compose/ui/text/TextStyle;
+Landroidx/compose/ui/text/TtsAnnotation;
+Landroidx/compose/ui/text/UrlAnnotation;
+Landroidx/compose/ui/text/VerbatimTtsAnnotation;
+Landroidx/compose/ui/text/android/BoringLayoutFactoryDefault;
+Landroidx/compose/ui/text/android/LayoutIntrinsics;
+Landroidx/compose/ui/text/android/Paint29$$ExternalSyntheticApiModelOutline0;
+Landroidx/compose/ui/text/android/Paint29;
+Landroidx/compose/ui/text/android/StaticLayoutFactory23;
+Landroidx/compose/ui/text/android/StaticLayoutFactory26;
+Landroidx/compose/ui/text/android/StaticLayoutFactory28;
+Landroidx/compose/ui/text/android/StaticLayoutFactoryImpl;
+Landroidx/compose/ui/text/android/StaticLayoutParams;
+Landroidx/compose/ui/text/android/TextAlignmentAdapter;
+Landroidx/compose/ui/text/android/TextAndroidCanvas;
+Landroidx/compose/ui/text/android/TextLayout;
+Landroidx/compose/ui/text/android/TextLayoutKt;
+Landroidx/compose/ui/text/android/style/LetterSpacingSpanEm;
+Landroidx/compose/ui/text/android/style/LetterSpacingSpanPx;
+Landroidx/compose/ui/text/android/style/LineHeightSpan;
+Landroidx/compose/ui/text/android/style/LineHeightStyleSpan;
+Landroidx/compose/ui/text/android/style/PlaceholderSpan;
+Landroidx/compose/ui/text/android/style/ShadowSpan;
+Landroidx/compose/ui/text/android/style/SkewXSpan;
+Landroidx/compose/ui/text/android/style/TextDecorationSpan;
+Landroidx/compose/ui/text/android/style/TypefaceSpan;
+Landroidx/compose/ui/text/caches/LruCache;
+Landroidx/compose/ui/text/caches/SimpleArrayMap;
+Landroidx/compose/ui/text/font/AndroidFontResolveInterceptor;
+Landroidx/compose/ui/text/font/AsyncTypefaceCache;
+Landroidx/compose/ui/text/font/DefaultFontFamily;
+Landroidx/compose/ui/text/font/Font$ResourceLoader;
+Landroidx/compose/ui/text/font/FontFamily$Resolver;
+Landroidx/compose/ui/text/font/FontFamily;
+Landroidx/compose/ui/text/font/FontFamilyResolverImpl;
+Landroidx/compose/ui/text/font/FontFamilyResolverKt;
+Landroidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter$special$$inlined$CoroutineExceptionHandler$1;
+Landroidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter;
+Landroidx/compose/ui/text/font/FontStyle;
+Landroidx/compose/ui/text/font/FontSynthesis;
+Landroidx/compose/ui/text/font/FontWeight;
+Landroidx/compose/ui/text/font/GenericFontFamily;
+Landroidx/compose/ui/text/font/PlatformResolveInterceptor$Companion$Default$1;
+Landroidx/compose/ui/text/font/PlatformResolveInterceptor$Companion;
+Landroidx/compose/ui/text/font/PlatformResolveInterceptor;
+Landroidx/compose/ui/text/font/PlatformTypefaces;
+Landroidx/compose/ui/text/font/SystemFontFamily;
+Landroidx/compose/ui/text/font/TypefaceRequest;
+Landroidx/compose/ui/text/font/TypefaceResult$Immutable;
+Landroidx/compose/ui/text/font/TypefaceResult;
+Landroidx/compose/ui/text/input/InputMethodManagerImpl;
+Landroidx/compose/ui/text/input/PlatformTextInputService;
+Landroidx/compose/ui/text/input/TextFieldValue;
+Landroidx/compose/ui/text/input/TextInputService;
+Landroidx/compose/ui/text/input/TextInputServiceAndroid;
+Landroidx/compose/ui/text/input/TextInputServiceAndroid_androidKt$$ExternalSyntheticLambda0;
+Landroidx/compose/ui/text/intl/AndroidLocale;
+Landroidx/compose/ui/text/intl/AndroidLocaleDelegateAPI24;
+Landroidx/compose/ui/text/intl/Locale;
+Landroidx/compose/ui/text/intl/LocaleList;
+Landroidx/compose/ui/text/intl/PlatformLocaleKt;
+Landroidx/compose/ui/text/platform/AndroidParagraphHelper_androidKt$NoopSpan$1;
+Landroidx/compose/ui/text/platform/AndroidParagraphHelper_androidKt;
+Landroidx/compose/ui/text/platform/AndroidParagraphIntrinsics$resolveTypeface$1;
+Landroidx/compose/ui/text/platform/AndroidParagraphIntrinsics;
+Landroidx/compose/ui/text/platform/AndroidTextPaint;
+Landroidx/compose/ui/text/platform/DefaultImpl$getFontLoadState$initCallback$1;
+Landroidx/compose/ui/text/platform/DefaultImpl;
+Landroidx/compose/ui/text/platform/EmojiCompatStatus;
+Landroidx/compose/ui/text/platform/ImmutableBool;
+Landroidx/compose/ui/text/platform/URLSpanCache;
+Landroidx/compose/ui/text/platform/extensions/LocaleListHelperMethods;
+Landroidx/compose/ui/text/platform/style/DrawStyleSpan;
+Landroidx/compose/ui/text/platform/style/ShaderBrushSpan;
+Landroidx/compose/ui/text/style/BaselineShift;
+Landroidx/compose/ui/text/style/BrushStyle;
+Landroidx/compose/ui/text/style/ColorStyle;
+Landroidx/compose/ui/text/style/Hyphens;
+Landroidx/compose/ui/text/style/LineBreak$Strategy;
+Landroidx/compose/ui/text/style/LineBreak$Strictness;
+Landroidx/compose/ui/text/style/LineBreak$WordBreak;
+Landroidx/compose/ui/text/style/LineBreak;
+Landroidx/compose/ui/text/style/LineHeightStyle$Alignment;
+Landroidx/compose/ui/text/style/LineHeightStyle;
+Landroidx/compose/ui/text/style/TextAlign;
+Landroidx/compose/ui/text/style/TextDecoration;
+Landroidx/compose/ui/text/style/TextDirection;
+Landroidx/compose/ui/text/style/TextForegroundStyle$Unspecified;
+Landroidx/compose/ui/text/style/TextForegroundStyle;
+Landroidx/compose/ui/text/style/TextGeometricTransform;
+Landroidx/compose/ui/text/style/TextIndent;
+Landroidx/compose/ui/text/style/TextMotion;
+Landroidx/compose/ui/unit/Constraints;
+Landroidx/compose/ui/unit/Density;
+Landroidx/compose/ui/unit/DensityImpl;
+Landroidx/compose/ui/unit/Dp$Companion;
+Landroidx/compose/ui/unit/Dp;
+Landroidx/compose/ui/unit/DpOffset;
+Landroidx/compose/ui/unit/DpRect;
+Landroidx/compose/ui/unit/IntOffset;
+Landroidx/compose/ui/unit/IntSize;
+Landroidx/compose/ui/unit/LayoutDirection;
+Landroidx/compose/ui/unit/TextUnit;
+Landroidx/compose/ui/unit/TextUnitType;
+Landroidx/core/app/ComponentActivity;
+Landroidx/core/app/CoreComponentFactory;
+Landroidx/core/content/res/ColorStateListInflaterCompat;
+Landroidx/core/content/res/ComplexColorCompat;
+Landroidx/core/graphics/TypefaceCompat;
+Landroidx/core/graphics/TypefaceCompatApi29Impl;
+Landroidx/core/graphics/TypefaceCompatUtil$Api19Impl;
+Landroidx/core/os/BuildCompat$Extensions30Impl$$ExternalSyntheticApiModelOutline0;
+Landroidx/core/os/BuildCompat$Extensions30Impl;
+Landroidx/core/os/BuildCompat;
+Landroidx/core/os/TraceCompat$Api18Impl;
+Landroidx/core/os/TraceCompat;
+Landroidx/core/provider/CallbackWithHandler$2;
+Landroidx/core/provider/FontProvider$Api16Impl;
+Landroidx/core/provider/FontRequest;
+Landroidx/core/provider/FontsContractCompat$FontInfo;
+Landroidx/core/text/TextUtilsCompat$Api17Impl;
+Landroidx/core/text/TextUtilsCompat;
+Landroidx/core/view/AccessibilityDelegateCompat$AccessibilityDelegateAdapter;
+Landroidx/core/view/AccessibilityDelegateCompat;
+Landroidx/core/view/MenuHostHelper;
+Landroidx/core/view/ViewCompat$$ExternalSyntheticLambda0;
+Landroidx/core/view/ViewCompat$Api29Impl;
+Landroidx/core/view/ViewCompat$Api30Impl;
+Landroidx/core/view/ViewCompat;
+Landroidx/core/view/accessibility/AccessibilityNodeProviderCompat;
+Landroidx/customview/poolingcontainer/PoolingContainerListenerHolder;
+Landroidx/emoji2/text/ConcurrencyHelpers$$ExternalSyntheticLambda0;
+Landroidx/emoji2/text/ConcurrencyHelpers$Handler28Impl;
+Landroidx/emoji2/text/DefaultGlyphChecker;
+Landroidx/emoji2/text/EmojiCompat$CompatInternal19$1;
+Landroidx/emoji2/text/EmojiCompat$CompatInternal19;
+Landroidx/emoji2/text/EmojiCompat$Config;
+Landroidx/emoji2/text/EmojiCompat$GlyphChecker;
+Landroidx/emoji2/text/EmojiCompat$MetadataRepoLoader;
+Landroidx/emoji2/text/EmojiCompat;
+Landroidx/emoji2/text/EmojiCompatInitializer$1;
+Landroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader$$ExternalSyntheticLambda0;
+Landroidx/emoji2/text/EmojiCompatInitializer$BackgroundDefaultLoader$1;
+Landroidx/emoji2/text/EmojiCompatInitializer$LoadEmojiCompatRunnable;
+Landroidx/emoji2/text/EmojiCompatInitializer;
+Landroidx/emoji2/text/EmojiProcessor$EmojiProcessAddSpanCallback;
+Landroidx/emoji2/text/EmojiProcessor$EmojiProcessCallback;
+Landroidx/emoji2/text/EmojiProcessor$ProcessorSm;
+Landroidx/emoji2/text/EmojiProcessor;
+Landroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader$$ExternalSyntheticLambda0;
+Landroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader$1;
+Landroidx/emoji2/text/FontRequestEmojiCompatConfig$FontRequestMetadataLoader;
+Landroidx/emoji2/text/FontRequestEmojiCompatConfig;
+Landroidx/emoji2/text/MetadataRepo$Node;
+Landroidx/emoji2/text/MetadataRepo;
+Landroidx/emoji2/text/TypefaceEmojiRasterizer;
+Landroidx/emoji2/text/TypefaceEmojiSpan;
+Landroidx/emoji2/text/UnprecomputeTextOnModificationSpannable;
+Landroidx/emoji2/text/flatbuffer/MetadataItem;
+Landroidx/emoji2/text/flatbuffer/MetadataList;
+Landroidx/emoji2/text/flatbuffer/Table;
+Landroidx/lifecycle/DefaultLifecycleObserver;
+Landroidx/lifecycle/DefaultLifecycleObserverAdapter$WhenMappings;
+Landroidx/lifecycle/DefaultLifecycleObserverAdapter;
+Landroidx/lifecycle/EmptyActivityLifecycleCallbacks;
+Landroidx/lifecycle/HasDefaultViewModelProviderFactory;
+Landroidx/lifecycle/Lifecycle$Event$Companion;
+Landroidx/lifecycle/Lifecycle$Event$WhenMappings;
+Landroidx/lifecycle/Lifecycle$Event;
+Landroidx/lifecycle/Lifecycle$State;
+Landroidx/lifecycle/Lifecycle;
+Landroidx/lifecycle/LifecycleDestroyedException;
+Landroidx/lifecycle/LifecycleDispatcher$DispatcherActivityCallback;
+Landroidx/lifecycle/LifecycleDispatcher;
+Landroidx/lifecycle/LifecycleEventObserver;
+Landroidx/lifecycle/LifecycleObserver;
+Landroidx/lifecycle/LifecycleOwner;
+Landroidx/lifecycle/LifecycleRegistry$ObserverWithState;
+Landroidx/lifecycle/LifecycleRegistry;
+Landroidx/lifecycle/Lifecycling;
+Landroidx/lifecycle/ProcessLifecycleInitializer;
+Landroidx/lifecycle/ProcessLifecycleOwner$Api29Impl;
+Landroidx/lifecycle/ProcessLifecycleOwner$attach$1$onActivityPreCreated$1;
+Landroidx/lifecycle/ProcessLifecycleOwner$attach$1;
+Landroidx/lifecycle/ProcessLifecycleOwner$initializationListener$1;
+Landroidx/lifecycle/ProcessLifecycleOwner;
+Landroidx/lifecycle/ReportFragment$LifecycleCallbacks$Companion;
+Landroidx/lifecycle/ReportFragment$LifecycleCallbacks;
+Landroidx/lifecycle/ReportFragment;
+Landroidx/lifecycle/SavedStateHandleAttacher;
+Landroidx/lifecycle/SavedStateHandlesProvider;
+Landroidx/lifecycle/SavedStateHandlesVM;
+Landroidx/lifecycle/ViewModelStore;
+Landroidx/lifecycle/ViewModelStoreOwner;
+Landroidx/lifecycle/viewmodel/CreationExtras$Empty;
+Landroidx/lifecycle/viewmodel/CreationExtras;
+Landroidx/lifecycle/viewmodel/MutableCreationExtras;
+Landroidx/lifecycle/viewmodel/ViewModelInitializer;
+Landroidx/metrics/performance/DelegatingFrameMetricsListener;
+Landroidx/metrics/performance/FrameData;
+Landroidx/metrics/performance/FrameDataApi24;
+Landroidx/metrics/performance/FrameDataApi31;
+Landroidx/metrics/performance/JankStats;
+Landroidx/metrics/performance/JankStatsApi16Impl;
+Landroidx/metrics/performance/JankStatsApi22Impl;
+Landroidx/metrics/performance/JankStatsApi24Impl$$ExternalSyntheticLambda0;
+Landroidx/metrics/performance/JankStatsApi24Impl;
+Landroidx/metrics/performance/JankStatsApi26Impl;
+Landroidx/metrics/performance/JankStatsApi31Impl;
+Landroidx/metrics/performance/PerformanceMetricsState$Holder;
+Landroidx/metrics/performance/PerformanceMetricsState;
+Landroidx/profileinstaller/ProfileInstaller$DiagnosticsCallback;
+Landroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda0;
+Landroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda1;
+Landroidx/profileinstaller/ProfileInstallerInitializer$Choreographer16Impl;
+Landroidx/profileinstaller/ProfileInstallerInitializer$Handler28Impl;
+Landroidx/profileinstaller/ProfileInstallerInitializer;
+Landroidx/savedstate/Recreator;
+Landroidx/savedstate/SavedStateRegistry$$ExternalSyntheticLambda0;
+Landroidx/savedstate/SavedStateRegistry$SavedStateProvider;
+Landroidx/savedstate/SavedStateRegistry;
+Landroidx/savedstate/SavedStateRegistryController;
+Landroidx/savedstate/SavedStateRegistryOwner;
+Landroidx/startup/AppInitializer;
+Landroidx/startup/InitializationProvider;
+Landroidx/startup/Initializer;
+Landroidx/tracing/Trace$$ExternalSyntheticApiModelOutline0;
+Landroidx/tv/foundation/PivotOffsets;
+Landroidx/tv/foundation/TvBringIntoViewSpec;
+Landroidx/tv/foundation/lazy/grid/LazyGridIntervalContent;
+Landroidx/tv/foundation/lazy/grid/LazyGridItemPlacementAnimator$onMeasured$$inlined$sortBy$1;
+Landroidx/tv/foundation/lazy/grid/LazyGridItemPlacementAnimator;
+Landroidx/tv/foundation/lazy/grid/LazyGridItemProviderImpl;
+Landroidx/tv/foundation/lazy/grid/LazyGridKt$rememberLazyGridMeasurePolicy$1$1$3;
+Landroidx/tv/foundation/lazy/grid/LazyGridScrollPosition;
+Landroidx/tv/foundation/lazy/grid/TvGridItemSpan;
+Landroidx/tv/foundation/lazy/grid/TvLazyGridItemSpanScope;
+Landroidx/tv/foundation/lazy/grid/TvLazyGridScope;
+Landroidx/tv/foundation/lazy/grid/TvLazyGridState$remeasurementModifier$1;
+Landroidx/tv/foundation/lazy/grid/TvLazyGridState;
+Landroidx/tv/foundation/lazy/layout/AwaitFirstLayoutModifier$waitForFirstLayout$1;
+Landroidx/tv/foundation/lazy/layout/AwaitFirstLayoutModifier;
+Landroidx/tv/foundation/lazy/layout/LazyLayoutBeyondBoundsInfo$Interval;
+Landroidx/tv/foundation/lazy/layout/LazyLayoutKeyIndexMap;
+Landroidx/tv/foundation/lazy/layout/LazyLayoutNearestRangeState;
+Landroidx/tv/foundation/lazy/layout/LazyLayoutSemanticState;
+Landroidx/tv/foundation/lazy/layout/LazyLayoutSemanticsKt$LazyLayoutSemanticState$1;
+Landroidx/tv/foundation/lazy/layout/LazyLayoutSemanticsKt$lazyLayoutSemantics$1$1;
+Landroidx/tv/foundation/lazy/layout/NearestRangeKeyIndexMap$2$1;
+Landroidx/tv/foundation/lazy/layout/NearestRangeKeyIndexMap;
+Landroidx/tv/foundation/lazy/list/EmptyLazyListLayoutInfo;
+Landroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal$Companion$emptyBeyondBoundsScope$1;
+Landroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal;
+Landroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsState;
+Landroidx/tv/foundation/lazy/list/LazyListBeyondBoundsState;
+Landroidx/tv/foundation/lazy/list/LazyListItemPlacementAnimator$onMeasured$$inlined$sortBy$2;
+Landroidx/tv/foundation/lazy/list/LazyListItemProviderImpl;
+Landroidx/tv/foundation/lazy/list/LazyListKt$rememberLazyListMeasurePolicy$1$1$measuredItemProvider$1;
+Landroidx/tv/foundation/lazy/list/LazyListKt$rememberLazyListMeasurePolicy$1$1;
+Landroidx/tv/foundation/lazy/list/LazyListMeasureResult;
+Landroidx/tv/foundation/lazy/list/LazyListMeasuredItem;
+Landroidx/tv/foundation/lazy/list/LazyListStateKt$rememberTvLazyListState$1$1;
+Landroidx/tv/foundation/lazy/list/LazyListStateKt;
+Landroidx/tv/foundation/lazy/list/TvLazyListInterval;
+Landroidx/tv/foundation/lazy/list/TvLazyListIntervalContent;
+Landroidx/tv/foundation/lazy/list/TvLazyListItemScopeImpl;
+Landroidx/tv/foundation/lazy/list/TvLazyListLayoutInfo;
+Landroidx/tv/foundation/lazy/list/TvLazyListScope;
+Landroidx/tv/foundation/lazy/list/TvLazyListState$scroll$1;
+Landroidx/tv/foundation/lazy/list/TvLazyListState;
+Landroidx/tv/material3/Border;
+Landroidx/tv/material3/BorderIndication;
+Landroidx/tv/material3/ColorScheme;
+Landroidx/tv/material3/ColorSchemeKt$LocalColorScheme$1;
+Landroidx/tv/material3/ColorSchemeKt;
+Landroidx/tv/material3/ContentColorKt;
+Landroidx/tv/material3/Glow;
+Landroidx/tv/material3/GlowIndication;
+Landroidx/tv/material3/GlowIndicationInstance;
+Landroidx/tv/material3/ScaleIndication;
+Landroidx/tv/material3/ScaleIndicationInstance;
+Landroidx/tv/material3/ScaleIndicationTokens;
+Landroidx/tv/material3/ShapesKt$LocalShapes$1;
+Landroidx/tv/material3/SurfaceKt$Surface$4;
+Landroidx/tv/material3/SurfaceKt$SurfaceImpl$2$2$1;
+Landroidx/tv/material3/SurfaceKt$SurfaceImpl$2$4$1;
+Landroidx/tv/material3/SurfaceKt$SurfaceImpl$2$5$1;
+Landroidx/tv/material3/SurfaceKt$SurfaceImpl$2;
+Landroidx/tv/material3/SurfaceKt$SurfaceImpl$3;
+Landroidx/tv/material3/SurfaceKt$handleDPadEnter$2$1;
+Landroidx/tv/material3/SurfaceKt$handleDPadEnter$2$2;
+Landroidx/tv/material3/SurfaceKt$handleDPadEnter$2;
+Landroidx/tv/material3/SurfaceKt$tvToggleable$1$1;
+Landroidx/tv/material3/SurfaceKt$tvToggleable$1$2;
+Landroidx/tv/material3/SurfaceKt$tvToggleable$1;
+Landroidx/tv/material3/SurfaceKt;
+Landroidx/tv/material3/TabColors;
+Landroidx/tv/material3/TabKt$Tab$1;
+Landroidx/tv/material3/TabKt$Tab$3$1;
+Landroidx/tv/material3/TabKt$Tab$4$1;
+Landroidx/tv/material3/TabKt$Tab$6;
+Landroidx/tv/material3/TabKt$Tab$7;
+Landroidx/tv/material3/TabKt;
+Landroidx/tv/material3/TabRowDefaults$PillIndicator$1;
+Landroidx/tv/material3/TabRowDefaults;
+Landroidx/tv/material3/TabRowKt$TabRow$1;
+Landroidx/tv/material3/TabRowKt$TabRow$2$1$1;
+Landroidx/tv/material3/TabRowKt$TabRow$2$2$1$1$2;
+Landroidx/tv/material3/TabRowKt$TabRow$2$2$1$1;
+Landroidx/tv/material3/TabRowKt$TabRow$2$2$1$separators$1;
+Landroidx/tv/material3/TabRowKt$TabRow$2$2$1;
+Landroidx/tv/material3/TabRowKt$TabRow$2;
+Landroidx/tv/material3/TabRowKt$TabRow$3;
+Landroidx/tv/material3/TabRowScopeImpl;
+Landroidx/tv/material3/TabRowSlots;
+Landroidx/tv/material3/TextKt$Text$1;
+Landroidx/tv/material3/TextKt$Text$2;
+Landroidx/tv/material3/TextKt;
+Landroidx/tv/material3/ToggleableSurfaceBorder;
+Landroidx/tv/material3/ToggleableSurfaceColors;
+Landroidx/tv/material3/ToggleableSurfaceGlow;
+Landroidx/tv/material3/ToggleableSurfaceScale;
+Landroidx/tv/material3/ToggleableSurfaceShape;
+Landroidx/tv/material3/tokens/ColorLightTokens;
+Landroidx/tv/material3/tokens/Elevation;
+Landroidx/tv/material3/tokens/PaletteTokens;
+Landroidx/tv/material3/tokens/ShapeTokens$BorderDefaultShape$1;
+Landroidx/tv/material3/tokens/ShapeTokens;
+Landroidx/tv/material3/tokens/TypographyTokensKt;
+Lcom/example/tvcomposebasedtests/ComposableSingletons$MainActivityKt;
+Lcom/example/tvcomposebasedtests/ComposableSingletons$UtilsKt$lambda-1$1;
+Lcom/example/tvcomposebasedtests/Config;
+Lcom/example/tvcomposebasedtests/JankStatsAggregator$listener$1;
+Lcom/example/tvcomposebasedtests/JankStatsAggregator;
+Lcom/example/tvcomposebasedtests/MainActivity$jankReportListener$1;
+Lcom/example/tvcomposebasedtests/MainActivity$startFrameMetrics$listener$1;
+Lcom/example/tvcomposebasedtests/MainActivity;
+Lcom/example/tvcomposebasedtests/UtilsKt$AddJankMetrics$1$2;
+Lcom/example/tvcomposebasedtests/UtilsKt$ScrollingRow$1$1$1;
+Lcom/example/tvcomposebasedtests/UtilsKt$ScrollingRow$2;
+Lcom/example/tvcomposebasedtests/UtilsKt;
+Lcom/example/tvcomposebasedtests/tvComponents/AppKt$App$1;
+Lcom/example/tvcomposebasedtests/tvComponents/ComposableSingletons$LazyContainersKt;
+Lcom/example/tvcomposebasedtests/tvComponents/ComposableSingletons$TopNavigationKt;
+Lcom/example/tvcomposebasedtests/tvComponents/Navigation;
+Lcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$PillIndicatorTabRow$1$1$1$1;
+Lcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$PillIndicatorTabRow$1;
+Lcom/example/tvcomposebasedtests/tvComponents/TopNavigationKt$TopNavigation$3$1;
+Lcom/google/gson/JsonIOException;
+Lcom/google/gson/internal/ConstructorConstructor;
+Lcom/google/gson/internal/LinkedTreeMap$1;
+Lcom/google/gson/internal/ObjectConstructor;
+Lkotlin/Function;
+Lkotlin/Lazy;
+Lkotlin/Pair;
+Lkotlin/Result$Failure;
+Lkotlin/Result;
+Lkotlin/ResultKt$$ExternalSyntheticCheckNotZero0;
+Lkotlin/ResultKt;
+Lkotlin/SynchronizedLazyImpl;
+Lkotlin/TuplesKt;
+Lkotlin/ULong$Companion;
+Lkotlin/UNINITIALIZED_VALUE;
+Lkotlin/Unit;
+Lkotlin/UnsafeLazyImpl;
+Lkotlin/collections/AbstractCollection;
+Lkotlin/collections/AbstractList;
+Lkotlin/collections/AbstractMap$toString$1;
+Lkotlin/collections/AbstractMap;
+Lkotlin/collections/AbstractMutableList;
+Lkotlin/collections/AbstractSet;
+Lkotlin/collections/ArrayDeque;
+Lkotlin/collections/ArraysKt___ArraysKt;
+Lkotlin/collections/CollectionsKt__MutableCollectionsJVMKt;
+Lkotlin/collections/CollectionsKt__ReversedViewsKt;
+Lkotlin/collections/CollectionsKt___CollectionsKt;
+Lkotlin/collections/EmptyList;
+Lkotlin/collections/EmptyMap;
+Lkotlin/coroutines/AbstractCoroutineContextElement;
+Lkotlin/coroutines/AbstractCoroutineContextKey;
+Lkotlin/coroutines/CombinedContext;
+Lkotlin/coroutines/Continuation;
+Lkotlin/coroutines/ContinuationInterceptor;
+Lkotlin/coroutines/CoroutineContext$Element;
+Lkotlin/coroutines/CoroutineContext$Key;
+Lkotlin/coroutines/CoroutineContext$plus$1;
+Lkotlin/coroutines/CoroutineContext;
+Lkotlin/coroutines/EmptyCoroutineContext;
+Lkotlin/coroutines/intrinsics/CoroutineSingletons;
+Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;
+Lkotlin/coroutines/jvm/internal/CompletedContinuation;
+Lkotlin/coroutines/jvm/internal/ContinuationImpl;
+Lkotlin/coroutines/jvm/internal/CoroutineStackFrame;
+Lkotlin/coroutines/jvm/internal/SuspendLambda;
+Lkotlin/jvm/functions/Function0;
+Lkotlin/jvm/functions/Function12;
+Lkotlin/jvm/functions/Function1;
+Lkotlin/jvm/functions/Function22;
+Lkotlin/jvm/functions/Function2;
+Lkotlin/jvm/functions/Function3;
+Lkotlin/jvm/functions/Function4;
+Lkotlin/jvm/functions/Function5;
+Lkotlin/jvm/internal/ArrayIterator;
+Lkotlin/jvm/internal/CallableReference$NoReceiver;
+Lkotlin/jvm/internal/CallableReference;
+Lkotlin/jvm/internal/ClassBasedDeclarationContainer;
+Lkotlin/jvm/internal/ClassReference;
+Lkotlin/jvm/internal/FunctionBase;
+Lkotlin/jvm/internal/FunctionReferenceImpl;
+Lkotlin/jvm/internal/Lambda;
+Lkotlin/jvm/internal/PropertyReference0Impl;
+Lkotlin/jvm/internal/PropertyReference;
+Lkotlin/jvm/internal/Ref$BooleanRef;
+Lkotlin/jvm/internal/Ref$ObjectRef;
+Lkotlin/jvm/internal/Reflection;
+Lkotlin/jvm/internal/ReflectionFactory;
+Lkotlin/jvm/internal/markers/KMappedMarker;
+Lkotlin/jvm/internal/markers/KMutableCollection;
+Lkotlin/jvm/internal/markers/KMutableMap;
+Lkotlin/math/MathKt;
+Lkotlin/random/FallbackThreadLocalRandom$implStorage$1;
+Lkotlin/ranges/IntProgression;
+Lkotlin/ranges/IntProgressionIterator;
+Lkotlin/ranges/IntRange;
+Lkotlin/reflect/KCallable;
+Lkotlin/reflect/KClass;
+Lkotlin/reflect/KFunction;
+Lkotlin/reflect/KProperty0;
+Lkotlin/reflect/KProperty;
+Lkotlin/sequences/ConstrainedOnceSequence;
+Lkotlin/sequences/FilteringSequence$iterator$1;
+Lkotlin/sequences/FilteringSequence;
+Lkotlin/sequences/GeneratorSequence$iterator$1;
+Lkotlin/sequences/GeneratorSequence;
+Lkotlin/sequences/Sequence;
+Lkotlin/sequences/SequencesKt;
+Lkotlin/sequences/SequencesKt__SequencesKt$asSequence$$inlined$Sequence$1;
+Lkotlin/sequences/TransformingSequence$iterator$1;
+Lkotlin/text/StringsKt__IndentKt$getIndentFunction$2;
+Lkotlin/text/StringsKt__RegexExtensionsKt;
+Lkotlin/text/StringsKt__StringBuilderKt;
+Lkotlin/text/StringsKt__StringNumberConversionsKt;
+Lkotlin/text/StringsKt__StringsKt;
+Lkotlin/text/StringsKt___StringsKt;
+Lkotlinx/coroutines/AbstractCoroutine;
+Lkotlinx/coroutines/Active;
+Lkotlinx/coroutines/BlockingCoroutine;
+Lkotlinx/coroutines/BlockingEventLoop;
+Lkotlinx/coroutines/CancelHandler;
+Lkotlinx/coroutines/CancellableContinuation;
+Lkotlinx/coroutines/CancellableContinuationImpl;
+Lkotlinx/coroutines/CancelledContinuation;
+Lkotlinx/coroutines/ChildContinuation;
+Lkotlinx/coroutines/ChildHandle;
+Lkotlinx/coroutines/ChildHandleNode;
+Lkotlinx/coroutines/ChildJob;
+Lkotlinx/coroutines/CompletableDeferredImpl;
+Lkotlinx/coroutines/CompletedContinuation;
+Lkotlinx/coroutines/CompletedExceptionally;
+Lkotlinx/coroutines/CompletedWithCancellation;
+Lkotlinx/coroutines/CoroutineContextKt$foldCopies$1;
+Lkotlinx/coroutines/CoroutineDispatcher$Key$1;
+Lkotlinx/coroutines/CoroutineDispatcher$Key;
+Lkotlinx/coroutines/CoroutineDispatcher;
+Lkotlinx/coroutines/CoroutineExceptionHandler;
+Lkotlinx/coroutines/CoroutineScope;
+Lkotlinx/coroutines/DefaultExecutor;
+Lkotlinx/coroutines/DefaultExecutorKt;
+Lkotlinx/coroutines/Delay;
+Lkotlinx/coroutines/DispatchedTask;
+Lkotlinx/coroutines/Dispatchers;
+Lkotlinx/coroutines/DisposableHandle;
+Lkotlinx/coroutines/Empty;
+Lkotlinx/coroutines/EventLoopImplBase;
+Lkotlinx/coroutines/EventLoopImplPlatform;
+Lkotlinx/coroutines/ExecutorCoroutineDispatcher$Key$1;
+Lkotlinx/coroutines/ExecutorCoroutineDispatcher;
+Lkotlinx/coroutines/GlobalScope;
+Lkotlinx/coroutines/InactiveNodeList;
+Lkotlinx/coroutines/Incomplete;
+Lkotlinx/coroutines/IncompleteStateBox;
+Lkotlinx/coroutines/InvokeOnCancel;
+Lkotlinx/coroutines/InvokeOnCancelling;
+Lkotlinx/coroutines/InvokeOnCompletion;
+Lkotlinx/coroutines/Job;
+Lkotlinx/coroutines/JobCancellingNode;
+Lkotlinx/coroutines/JobImpl;
+Lkotlinx/coroutines/JobNode;
+Lkotlinx/coroutines/JobSupport$ChildCompletion;
+Lkotlinx/coroutines/JobSupport$Finishing;
+Lkotlinx/coroutines/JobSupport$addLastAtomic$$inlined$addLastIf$1;
+Lkotlinx/coroutines/JobSupport;
+Lkotlinx/coroutines/MainCoroutineDispatcher;
+Lkotlinx/coroutines/NodeList;
+Lkotlinx/coroutines/NonDisposableHandle;
+Lkotlinx/coroutines/NotCompleted;
+Lkotlinx/coroutines/ParentJob;
+Lkotlinx/coroutines/StandaloneCoroutine;
+Lkotlinx/coroutines/SupervisorJobImpl;
+Lkotlinx/coroutines/ThreadLocalEventLoop;
+Lkotlinx/coroutines/TimeoutCancellationException;
+Lkotlinx/coroutines/Unconfined;
+Lkotlinx/coroutines/UndispatchedCoroutine;
+Lkotlinx/coroutines/UndispatchedMarker;
+Lkotlinx/coroutines/Waiter;
+Lkotlinx/coroutines/android/AndroidDispatcherFactory;
+Lkotlinx/coroutines/android/HandlerContext;
+Lkotlinx/coroutines/android/HandlerDispatcher;
+Lkotlinx/coroutines/android/HandlerDispatcherKt;
+Lkotlinx/coroutines/channels/BufferOverflow;
+Lkotlinx/coroutines/channels/BufferedChannel$BufferedChannelIterator;
+Lkotlinx/coroutines/channels/BufferedChannel;
+Lkotlinx/coroutines/channels/BufferedChannelKt$createSegmentFunction$1;
+Lkotlinx/coroutines/channels/BufferedChannelKt;
+Lkotlinx/coroutines/channels/Channel$Factory;
+Lkotlinx/coroutines/channels/Channel;
+Lkotlinx/coroutines/channels/ChannelResult$Closed;
+Lkotlinx/coroutines/channels/ChannelResult$Failed;
+Lkotlinx/coroutines/channels/ChannelSegment;
+Lkotlinx/coroutines/channels/ConflatedBufferedChannel;
+Lkotlinx/coroutines/channels/ProducerCoroutine;
+Lkotlinx/coroutines/channels/ProducerScope;
+Lkotlinx/coroutines/channels/ReceiveChannel;
+Lkotlinx/coroutines/channels/SendChannel;
+Lkotlinx/coroutines/channels/WaiterEB;
+Lkotlinx/coroutines/flow/AbstractFlow$collect$1;
+Lkotlinx/coroutines/flow/DistinctFlowImpl$collect$2$emit$1;
+Lkotlinx/coroutines/flow/DistinctFlowImpl$collect$2;
+Lkotlinx/coroutines/flow/DistinctFlowImpl;
+Lkotlinx/coroutines/flow/Flow;
+Lkotlinx/coroutines/flow/FlowCollector;
+Lkotlinx/coroutines/flow/FlowKt__ChannelsKt$emitAllImpl$1;
+Lkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$$inlined$unsafeFlow$1;
+Lkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$1$1$emit$1;
+Lkotlinx/coroutines/flow/FlowKt__LimitKt$dropWhile$1$1;
+Lkotlinx/coroutines/flow/FlowKt__MergeKt$mapLatest$1;
+Lkotlinx/coroutines/flow/FlowKt__MergeKt;
+Lkotlinx/coroutines/flow/FlowKt__ReduceKt$first$$inlined$collectWhile$2$1;
+Lkotlinx/coroutines/flow/FlowKt__ReduceKt$first$$inlined$collectWhile$2;
+Lkotlinx/coroutines/flow/FlowKt__ReduceKt$first$3;
+Lkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1$2;
+Lkotlinx/coroutines/flow/FlowKt__ShareKt$launchSharing$1;
+Lkotlinx/coroutines/flow/MutableSharedFlow;
+Lkotlinx/coroutines/flow/ReadonlyStateFlow;
+Lkotlinx/coroutines/flow/SafeFlow;
+Lkotlinx/coroutines/flow/SharedFlowImpl$Emitter;
+Lkotlinx/coroutines/flow/SharedFlowImpl$collect$1;
+Lkotlinx/coroutines/flow/SharedFlowImpl;
+Lkotlinx/coroutines/flow/SharedFlowSlot;
+Lkotlinx/coroutines/flow/SharingCommand;
+Lkotlinx/coroutines/flow/SharingConfig;
+Lkotlinx/coroutines/flow/SharingStarted;
+Lkotlinx/coroutines/flow/StartedLazily;
+Lkotlinx/coroutines/flow/StartedWhileSubscribed$command$1;
+Lkotlinx/coroutines/flow/StartedWhileSubscribed$command$2;
+Lkotlinx/coroutines/flow/StartedWhileSubscribed;
+Lkotlinx/coroutines/flow/StateFlow;
+Lkotlinx/coroutines/flow/StateFlowImpl$collect$1;
+Lkotlinx/coroutines/flow/StateFlowImpl;
+Lkotlinx/coroutines/flow/StateFlowSlot;
+Lkotlinx/coroutines/flow/internal/AbortFlowException;
+Lkotlinx/coroutines/flow/internal/AbstractSharedFlow;
+Lkotlinx/coroutines/flow/internal/AbstractSharedFlowSlot;
+Lkotlinx/coroutines/flow/internal/ChannelFlow$collect$2;
+Lkotlinx/coroutines/flow/internal/ChannelFlow$collectToFun$1;
+Lkotlinx/coroutines/flow/internal/ChannelFlow;
+Lkotlinx/coroutines/flow/internal/ChannelFlowOperator;
+Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1$2;
+Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1$emit$1;
+Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3$1;
+Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest$flowCollect$3;
+Lkotlinx/coroutines/flow/internal/ChannelFlowTransformLatest;
+Lkotlinx/coroutines/flow/internal/FusibleFlow;
+Lkotlinx/coroutines/flow/internal/NoOpContinuation;
+Lkotlinx/coroutines/flow/internal/NopCollector;
+Lkotlinx/coroutines/flow/internal/SafeCollector;
+Lkotlinx/coroutines/flow/internal/SendingCollector;
+Lkotlinx/coroutines/flow/internal/SubscriptionCountStateFlow;
+Lkotlinx/coroutines/internal/AtomicOp;
+Lkotlinx/coroutines/internal/ConcurrentLinkedListNode;
+Lkotlinx/coroutines/internal/ContextScope;
+Lkotlinx/coroutines/internal/DispatchedContinuation;
+Lkotlinx/coroutines/internal/LimitedDispatcher;
+Lkotlinx/coroutines/internal/LockFreeLinkedListNode$toString$1;
+Lkotlinx/coroutines/internal/LockFreeLinkedListNode;
+Lkotlinx/coroutines/internal/LockFreeTaskQueue;
+Lkotlinx/coroutines/internal/LockFreeTaskQueueCore;
+Lkotlinx/coroutines/internal/MainDispatcherFactory;
+Lkotlinx/coroutines/internal/MainDispatcherLoader;
+Lkotlinx/coroutines/internal/OpDescriptor;
+Lkotlinx/coroutines/internal/Removed;
+Lkotlinx/coroutines/internal/ResizableAtomicArray;
+Lkotlinx/coroutines/internal/ScopeCoroutine;
+Lkotlinx/coroutines/internal/Segment;
+Lkotlinx/coroutines/internal/StackTraceRecoveryKt;
+Lkotlinx/coroutines/internal/Symbol;
+Lkotlinx/coroutines/internal/SystemPropsKt__SystemPropsKt;
+Lkotlinx/coroutines/internal/ThreadState;
+Lkotlinx/coroutines/scheduling/CoroutineScheduler;
+Lkotlinx/coroutines/scheduling/DefaultIoScheduler;
+Lkotlinx/coroutines/scheduling/DefaultScheduler;
+Lkotlinx/coroutines/scheduling/GlobalQueue;
+Lkotlinx/coroutines/scheduling/NanoTimeSource;
+Lkotlinx/coroutines/scheduling/SchedulerCoroutineDispatcher;
+Lkotlinx/coroutines/scheduling/Task;
+Lkotlinx/coroutines/scheduling/TasksKt;
+Lkotlinx/coroutines/scheduling/UnlimitedIoScheduler;
+Lkotlinx/coroutines/sync/Mutex;
+Lkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner$resume$2;
+Lkotlinx/coroutines/sync/MutexImpl$CancellableContinuationWithOwner;
+Lkotlinx/coroutines/sync/MutexImpl;
+Lkotlinx/coroutines/sync/SemaphoreImpl$addAcquireToQueue$createNewSegment$1;
+Lkotlinx/coroutines/sync/SemaphoreImpl$tryResumeNextFromQueue$createNewSegment$1;
+Lkotlinx/coroutines/sync/SemaphoreImpl;
+Lkotlinx/coroutines/sync/SemaphoreKt;
+Lkotlinx/coroutines/sync/SemaphoreSegment;
+Lokhttp3/Headers$Builder;
+Lokhttp3/MediaType;
+PL_COROUTINE/ArtificialStackFrames;->access$removeRunning(Landroidx/compose/runtime/Stack;)V
+PL_COROUTINE/ArtificialStackFrames;->moveGroup(Landroidx/compose/runtime/SlotWriter;ILandroidx/compose/runtime/SlotWriter;ZZZ)Ljava/util/List;
+PLandroidx/activity/ComponentActivity$ReportFullyDrawnExecutorApi16Impl;->run()V
+PLandroidx/arch/core/internal/SafeIterableMap$ListIterator;->next()Ljava/lang/Object;
+PLandroidx/arch/core/internal/SafeIterableMap$ListIterator;->nextNode()Landroidx/arch/core/internal/SafeIterableMap$Entry;
+PLandroidx/arch/core/internal/SafeIterableMap$ListIterator;->supportRemove(Landroidx/arch/core/internal/SafeIterableMap$Entry;)V
+PLandroidx/collection/ArrayMap$EntrySet;->iterator()Ljava/util/Iterator;
+PLandroidx/compose/foundation/DrawOverscrollModifier;->equals(Ljava/lang/Object;)Z
+PLandroidx/compose/foundation/OverscrollConfiguration;->equals(Ljava/lang/Object;)Z
+PLandroidx/compose/foundation/ScrollingLayoutElement;->equals(Ljava/lang/Object;)Z
+PLandroidx/compose/foundation/gestures/BringIntoViewRequestPriorityQueue;->resumeAndRemoveAll()V
+PLandroidx/compose/foundation/gestures/DraggableNode;->disposeInteractionSource()V
+PLandroidx/compose/foundation/gestures/DraggableNode;->onDetach()V
+PLandroidx/compose/foundation/gestures/ScrollableElement;->equals(Ljava/lang/Object;)Z
+PLandroidx/compose/foundation/layout/OffsetElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+PLandroidx/compose/foundation/layout/SizeElement;->update(Landroidx/compose/ui/Modifier$Node;)V
+PLandroidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher;->onForgotten()V
+PLandroidx/compose/runtime/ComposerImpl$CompositionContextHolder;->onForgotten()V
+PLandroidx/compose/runtime/ComposerImpl$CompositionContextImpl;->dispose()V
+PLandroidx/compose/runtime/ComposerImpl;->recordDelete()V
+PLandroidx/compose/runtime/ComposerImpl;->reportFreeMovableContent$reportGroup(Landroidx/compose/runtime/ComposerImpl;IZI)I
+PLandroidx/compose/runtime/ComposerImpl;->reportFreeMovableContent(I)V
+PLandroidx/compose/runtime/CompositionContext;->unregisterComposer$runtime_release(Landroidx/compose/runtime/Composer;)V
+PLandroidx/compose/runtime/CompositionScopedCoroutineScopeCanceller;->onForgotten()V
+PLandroidx/compose/runtime/Pending;->nodePositionOf(Landroidx/compose/runtime/KeyInfo;)I
+PLandroidx/compose/runtime/Pending;->updateNodeCount(II)Z
+PLandroidx/compose/runtime/Recomposer$effectJob$1$1;->invoke(Ljava/lang/Throwable;)V
+PLandroidx/compose/runtime/Recomposer;->cancel()V
+PLandroidx/compose/runtime/Recomposer;->reportRemovedComposition$runtime_release(Landroidx/compose/runtime/CompositionImpl;)V
+PLandroidx/compose/runtime/SlotWriter;->ensureStarted(I)V
+PLandroidx/compose/runtime/SlotWriter;->startGroup()V
+PLandroidx/compose/runtime/changelist/ComposerChangeListWriter;->removeNode(II)V
+PLandroidx/compose/runtime/changelist/Operation$EndCompositionScope;-><clinit>()V
+PLandroidx/compose/runtime/changelist/Operation$EndCompositionScope;-><init>()V
+PLandroidx/compose/runtime/changelist/Operation$EndCompositionScope;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+PLandroidx/compose/runtime/changelist/Operation$EndCurrentGroup;-><clinit>()V
+PLandroidx/compose/runtime/changelist/Operation$EndCurrentGroup;-><init>()V
+PLandroidx/compose/runtime/changelist/Operation$EndCurrentGroup;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+PLandroidx/compose/runtime/changelist/Operation$EnsureGroupStarted;-><clinit>()V
+PLandroidx/compose/runtime/changelist/Operation$EnsureGroupStarted;-><init>()V
+PLandroidx/compose/runtime/changelist/Operation$EnsureGroupStarted;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+PLandroidx/compose/runtime/changelist/Operation$EnsureRootGroupStarted;-><clinit>()V
+PLandroidx/compose/runtime/changelist/Operation$EnsureRootGroupStarted;-><init>()V
+PLandroidx/compose/runtime/changelist/Operation$EnsureRootGroupStarted;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+PLandroidx/compose/runtime/changelist/Operation$RemoveCurrentGroup;-><clinit>()V
+PLandroidx/compose/runtime/changelist/Operation$RemoveCurrentGroup;-><init>()V
+PLandroidx/compose/runtime/changelist/Operation$RemoveCurrentGroup;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+PLandroidx/compose/runtime/changelist/Operation$RemoveNode;-><clinit>()V
+PLandroidx/compose/runtime/changelist/Operation$RemoveNode;-><init>()V
+PLandroidx/compose/runtime/changelist/Operation$RemoveNode;->execute(Landroidx/compose/runtime/changelist/Operations$OpIterator;Landroidx/compose/runtime/Applier;Landroidx/compose/runtime/SlotWriter;Landroidx/compose/runtime/CompositionImpl$RememberEventDispatcher;)V
+PLandroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;->remove(IILandroidx/compose/runtime/Stack;)Landroidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/TrieNode;
+PLandroidx/compose/runtime/saveable/SaveableHolder;->onForgotten()V
+PLandroidx/compose/runtime/saveable/SaveableStateRegistryImpl$registerProvider$3;->unregister()V
+PLandroidx/compose/runtime/snapshots/Snapshot$Companion$$ExternalSyntheticLambda0;->dispose()V
+PLandroidx/compose/runtime/snapshots/SnapshotIdSet$iterator$1;-><init>(Landroidx/compose/runtime/snapshots/SnapshotIdSet;Lkotlin/coroutines/Continuation;)V
+PLandroidx/compose/runtime/snapshots/SnapshotIdSet$iterator$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
+PLandroidx/compose/runtime/snapshots/SnapshotIdSet$iterator$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+PLandroidx/compose/runtime/snapshots/SnapshotIdSet;->iterator()Ljava/util/Iterator;
+PLandroidx/compose/ui/autofill/AutofillCallback;->unregister(Landroidx/compose/ui/autofill/AndroidAutofill;)V
+PLandroidx/compose/ui/focus/FocusOwnerImpl;->clearFocus(ZZ)V
+PLandroidx/compose/ui/input/nestedscroll/NestedScrollNode;->onDetach()V
+PLandroidx/compose/ui/input/pointer/SuspendPointerInputElement;->equals(Ljava/lang/Object;)Z
+PLandroidx/compose/ui/input/pointer/SuspendingPointerInputModifierNodeImpl;->onDetach()V
+PLandroidx/compose/ui/input/pointer/SuspendingPointerInputModifierNodeImpl;->resetPointerInputHandler()V
+PLandroidx/compose/ui/layout/OnSizeChangedModifier;->equals(Ljava/lang/Object;)Z
+PLandroidx/compose/ui/modifier/ModifierLocalManager;->invalidate()V
+PLandroidx/compose/ui/node/BackwardsCompatNode;->onDetach()V
+PLandroidx/compose/ui/node/MeasureAndLayoutDelegate$PostponedRequest;-><init>(Landroidx/compose/ui/node/LayoutNode;ZZ)V
+PLandroidx/compose/ui/node/UiApplier;->remove(II)V
+PLandroidx/compose/ui/platform/AndroidComposeView;->getModifierLocalManager()Landroidx/compose/ui/modifier/ModifierLocalManager;
+PLandroidx/compose/ui/platform/AndroidComposeView;->onDetachedFromWindow()V
+PLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$1;->onViewDetachedFromWindow(Landroid/view/View;)V
+PLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat$boundsUpdatesEventLoop$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+PLandroidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat;->onStop(Landroidx/lifecycle/LifecycleOwner;)V
+PLandroidx/compose/ui/platform/DisposableSaveableStateRegistry_androidKt$DisposableSaveableStateRegistry$1;->invoke()Ljava/lang/Object;
+PLandroidx/compose/ui/platform/WeakCache;-><init>(Lcom/google/gson/internal/ConstructorConstructor;Ljava/lang/Class;)V
+PLandroidx/compose/ui/platform/WindowRecomposer_androidKt$createLifecycleAwareWindowRecomposer$1;->onViewDetachedFromWindow(Landroid/view/View;)V
+PLandroidx/compose/ui/platform/WrappedComposition;->dispose()V
+PLandroidx/compose/ui/text/PlatformTextStyle;->equals(Ljava/lang/Object;)Z
+PLandroidx/concurrent/futures/AbstractResolvableFuture$Listener;-><clinit>()V
+PLandroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper;-><init>(Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;)V
+PLandroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper;->casListeners(Landroidx/concurrent/futures/AbstractResolvableFuture;Landroidx/concurrent/futures/AbstractResolvableFuture$Listener;)Z
+PLandroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper;->casValue(Landroidx/concurrent/futures/AbstractResolvableFuture;Ljava/lang/Object;Ljava/lang/Object;)Z
+PLandroidx/concurrent/futures/AbstractResolvableFuture$SafeAtomicHelper;->casWaiters(Landroidx/concurrent/futures/AbstractResolvableFuture;Landroidx/concurrent/futures/AbstractResolvableFuture$Waiter;Landroidx/concurrent/futures/AbstractResolvableFuture$Waiter;)Z
+PLandroidx/concurrent/futures/AbstractResolvableFuture$Waiter;-><clinit>()V
+PLandroidx/concurrent/futures/AbstractResolvableFuture$Waiter;-><init>(I)V
+PLandroidx/concurrent/futures/AbstractResolvableFuture;-><clinit>()V
+PLandroidx/concurrent/futures/AbstractResolvableFuture;->complete(Landroidx/concurrent/futures/AbstractResolvableFuture;)V
+PLandroidx/core/view/ViewKt$ancestors$1;-><clinit>()V
+PLandroidx/core/view/ViewKt$ancestors$1;-><init>()V
+PLandroidx/core/view/ViewKt$ancestors$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+PLandroidx/lifecycle/DefaultLifecycleObserver;->onDestroy(Landroidx/lifecycle/LifecycleOwner;)V
+PLandroidx/lifecycle/DefaultLifecycleObserver;->onStop(Landroidx/lifecycle/LifecycleOwner;)V
+PLandroidx/lifecycle/EmptyActivityLifecycleCallbacks;->onActivityDestroyed(Landroid/app/Activity;)V
+PLandroidx/lifecycle/EmptyActivityLifecycleCallbacks;->onActivityPaused(Landroid/app/Activity;)V
+PLandroidx/lifecycle/EmptyActivityLifecycleCallbacks;->onActivityStopped(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ProcessLifecycleOwner$attach$1;->onActivityPaused(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ProcessLifecycleOwner$attach$1;->onActivityStopped(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityDestroyed(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityPaused(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityPreDestroyed(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityPrePaused(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityPreStopped(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ReportFragment$LifecycleCallbacks;->onActivityStopped(Landroid/app/Activity;)V
+PLandroidx/lifecycle/ReportFragment;->onDestroy()V
+PLandroidx/lifecycle/ReportFragment;->onPause()V
+PLandroidx/lifecycle/ReportFragment;->onStop()V
+PLandroidx/metrics/performance/JankStatsApi24Impl;->removeFrameMetricsListenerDelegate(Landroidx/metrics/performance/JankStatsApi24Impl$$ExternalSyntheticLambda0;Landroid/view/Window;)V
+PLandroidx/profileinstaller/ProfileInstaller$$ExternalSyntheticLambda1;-><init>(I)V
+PLandroidx/profileinstaller/ProfileInstallerInitializer$$ExternalSyntheticLambda1;->run()V
+PLandroidx/profileinstaller/ProfileVerifier$Cache;-><init>(IIJJ)V
+PLandroidx/profileinstaller/ProfileVerifier$Cache;->writeOnFile(Ljava/io/File;)V
+PLandroidx/profileinstaller/ProfileVerifier;-><clinit>()V
+PLandroidx/profileinstaller/ProfileVerifier;->setCompilationStatus(IZZ)Landroidx/compose/ui/unit/Dp$Companion;
+PLandroidx/profileinstaller/ProfileVerifier;->writeProfileVerification(Landroid/content/Context;Z)V
+PLandroidx/tv/foundation/lazy/layout/LazyLayoutBeyondBoundsInfo$Interval;->equals(Ljava/lang/Object;)Z
+PLandroidx/tv/foundation/lazy/list/LazyLayoutBeyondBoundsModifierLocal;->getValue()Ljava/lang/Object;
+PLcom/example/tvcomposebasedtests/MainActivity;->onPause()V
+PLcom/google/gson/FieldNamingPolicy$1;-><init>()V
+PLcom/google/gson/FieldNamingPolicy$1;->translateName(Ljava/lang/reflect/Field;)Ljava/lang/String;
+PLcom/google/gson/FieldNamingPolicy$2;-><init>()V
+PLcom/google/gson/FieldNamingPolicy$3;-><init>()V
+PLcom/google/gson/FieldNamingPolicy$4;-><init>()V
+PLcom/google/gson/FieldNamingPolicy$5;-><init>()V
+PLcom/google/gson/FieldNamingPolicy$6;-><init>()V
+PLcom/google/gson/FieldNamingPolicy$7;-><init>()V
+PLcom/google/gson/FieldNamingPolicy;-><clinit>()V
+PLcom/google/gson/FieldNamingPolicy;-><init>(Ljava/lang/String;I)V
+PLcom/google/gson/Gson$1;-><init>(I)V
+PLcom/google/gson/Gson$3;-><init>(I)V
+PLcom/google/gson/Gson$4;-><init>(Lcom/google/gson/TypeAdapter;I)V
+PLcom/google/gson/Gson$FutureTypeAdapter;-><init>()V
+PLcom/google/gson/Gson;-><init>()V
+PLcom/google/gson/Gson;->newJsonWriter(Ljava/io/Writer;)Lcom/google/gson/stream/JsonWriter;
+PLcom/google/gson/Gson;->toJson(Lcom/google/gson/JsonObject;Lcom/google/gson/stream/JsonWriter;)V
+PLcom/google/gson/JsonNull;-><clinit>()V
+PLcom/google/gson/JsonPrimitive;-><init>(Ljava/lang/String;)V
+PLcom/google/gson/ToNumberPolicy$1;-><init>()V
+PLcom/google/gson/ToNumberPolicy$2;-><init>()V
+PLcom/google/gson/ToNumberPolicy$3;-><init>()V
+PLcom/google/gson/ToNumberPolicy$4;-><init>()V
+PLcom/google/gson/ToNumberPolicy;-><clinit>()V
+PLcom/google/gson/ToNumberPolicy;-><init>(Ljava/lang/String;I)V
+PLcom/google/gson/TypeAdapter;->nullSafe()Lcom/google/gson/Gson$4;
+PLcom/google/gson/internal/$Gson$Types$ParameterizedTypeImpl;-><init>(Ljava/lang/reflect/Type;Ljava/lang/reflect/Type;[Ljava/lang/reflect/Type;)V
+PLcom/google/gson/internal/$Gson$Types$ParameterizedTypeImpl;->getActualTypeArguments()[Ljava/lang/reflect/Type;
+PLcom/google/gson/internal/ConstructorConstructor;-><init>(Ljava/util/Map;Ljava/util/List;)V
+PLcom/google/gson/internal/ConstructorConstructor;->checkInstantiable(Ljava/lang/Class;)Ljava/lang/String;
+PLcom/google/gson/internal/ConstructorConstructor;->get(Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/internal/ObjectConstructor;
+PLcom/google/gson/internal/Excluder;-><clinit>()V
+PLcom/google/gson/internal/Excluder;-><init>()V
+PLcom/google/gson/internal/Excluder;->create(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+PLcom/google/gson/internal/Excluder;->excludeClassInStrategy(Z)V
+PLcom/google/gson/internal/Excluder;->isAnonymousOrNonStaticLocal(Ljava/lang/Class;)Z
+PLcom/google/gson/internal/LinkedTreeMap$KeySet$1;-><init>(Landroidx/collection/ArrayMap$EntrySet;)V
+PLcom/google/gson/internal/LinkedTreeMap$KeySet$1;->next()Ljava/lang/Object;
+PLcom/google/gson/internal/LinkedTreeMap$LinkedTreeMapIterator;->nextNode()Lcom/google/gson/internal/LinkedTreeMap$Node;
+PLcom/google/gson/internal/LinkedTreeMap$Node;->getKey()Ljava/lang/Object;
+PLcom/google/gson/internal/LinkedTreeMap;-><clinit>()V
+PLcom/google/gson/internal/bind/ArrayTypeAdapter;-><clinit>()V
+PLcom/google/gson/internal/bind/CollectionTypeAdapterFactory;-><init>(Lcom/google/gson/internal/ConstructorConstructor;I)V
+PLcom/google/gson/internal/bind/CollectionTypeAdapterFactory;->create(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+PLcom/google/gson/internal/bind/DateTypeAdapter;-><clinit>()V
+PLcom/google/gson/internal/bind/JsonTreeWriter$1;-><init>()V
+PLcom/google/gson/internal/bind/JsonTreeWriter;-><clinit>()V
+PLcom/google/gson/internal/bind/JsonTreeWriter;->beginObject()V
+PLcom/google/gson/internal/bind/JsonTreeWriter;->value(Ljava/lang/Boolean;)V
+PLcom/google/gson/internal/bind/MapTypeAdapterFactory;-><init>(Lcom/google/gson/internal/ConstructorConstructor;)V
+PLcom/google/gson/internal/bind/MapTypeAdapterFactory;->create(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+PLcom/google/gson/internal/bind/NumberTypeAdapter$1;-><init>(ILjava/lang/Object;)V
+PLcom/google/gson/internal/bind/NumberTypeAdapter$1;->create(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+PLcom/google/gson/internal/bind/NumberTypeAdapter;-><clinit>()V
+PLcom/google/gson/internal/bind/ObjectTypeAdapter;-><clinit>()V
+PLcom/google/gson/internal/bind/ObjectTypeAdapter;-><init>(Lcom/google/gson/Gson;)V
+PLcom/google/gson/internal/bind/ReflectiveTypeAdapterFactory$1;-><init>(Ljava/lang/String;Ljava/lang/reflect/Field;ZZLjava/lang/reflect/Method;ZLcom/google/gson/TypeAdapter;Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)V
+PLcom/google/gson/internal/bind/ReflectiveTypeAdapterFactory$Adapter;-><init>(Ljava/util/LinkedHashMap;)V
+PLcom/google/gson/internal/bind/ReflectiveTypeAdapterFactory;-><init>(Lcom/google/gson/internal/ConstructorConstructor;Lcom/google/gson/internal/Excluder;Lcom/google/gson/internal/bind/CollectionTypeAdapterFactory;Ljava/util/List;)V
+PLcom/google/gson/internal/bind/ReflectiveTypeAdapterFactory;->create(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+PLcom/google/gson/internal/bind/ReflectiveTypeAdapterFactory;->getBoundFields(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;Ljava/lang/Class;Z)Ljava/util/LinkedHashMap;
+PLcom/google/gson/internal/bind/ReflectiveTypeAdapterFactory;->includeField(Ljava/lang/reflect/Field;Z)Z
+PLcom/google/gson/internal/bind/TypeAdapters$29;-><init>(I)V
+PLcom/google/gson/internal/bind/TypeAdapters$29;->create(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+PLcom/google/gson/internal/bind/TypeAdapters$31;-><init>(Ljava/lang/Class;Lcom/google/gson/TypeAdapter;I)V
+PLcom/google/gson/internal/bind/TypeAdapters$31;->create(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+PLcom/google/gson/internal/bind/TypeAdapters$32;-><init>(Ljava/lang/Class;Ljava/lang/Class;Lcom/google/gson/TypeAdapter;I)V
+PLcom/google/gson/internal/bind/TypeAdapters$32;->create(Lcom/google/gson/Gson;Lcom/google/gson/reflect/TypeToken;)Lcom/google/gson/TypeAdapter;
+PLcom/google/gson/internal/bind/TypeAdapters$34$1;-><init>(Lcom/google/gson/Gson;Ljava/lang/reflect/Type;Lcom/google/gson/TypeAdapter;Lcom/google/gson/internal/ObjectConstructor;)V
+PLcom/google/gson/internal/bind/TypeAdapters;-><clinit>()V
+PLcom/google/gson/internal/bind/TypeAdapters;->newFactory(Ljava/lang/Class;Lcom/google/gson/TypeAdapter;)Lcom/google/gson/internal/bind/TypeAdapters$31;
+PLcom/google/gson/internal/bind/TypeAdapters;->newFactory(Ljava/lang/Class;Ljava/lang/Class;Lcom/google/gson/TypeAdapter;)Lcom/google/gson/internal/bind/TypeAdapters$32;
+PLcom/google/gson/internal/reflect/ReflectionHelper$RecordNotSupportedHelper;-><init>()V
+PLcom/google/gson/internal/reflect/ReflectionHelper$RecordNotSupportedHelper;->isRecord(Ljava/lang/Class;)Z
+PLcom/google/gson/internal/reflect/ReflectionHelper$RecordSupportedHelper;-><init>()V
+PLcom/google/gson/internal/reflect/ReflectionHelper;-><clinit>()V
+PLcom/google/gson/internal/reflect/ReflectionHelper;->makeAccessible(Ljava/lang/reflect/AccessibleObject;)V
+PLcom/google/gson/internal/sql/SqlDateTypeAdapter;-><clinit>()V
+PLcom/google/gson/internal/sql/SqlTimeTypeAdapter;-><clinit>()V
+PLcom/google/gson/internal/sql/SqlTimestampTypeAdapter;-><clinit>()V
+PLcom/google/gson/internal/sql/SqlTypesSupport;-><clinit>()V
+PLcom/google/gson/stream/JsonWriter;-><clinit>()V
+PLcom/google/gson/stream/JsonWriter;->endArray()V
+PLcom/google/gson/stream/JsonWriter;->name(Ljava/lang/String;)V
+PLcom/google/gson/stream/JsonWriter;->newline()V
+PLkotlin/ResultKt;->SampleTvLazyColumn(ILandroidx/compose/runtime/Composer;I)V
+PLkotlin/ResultKt;->TvLazyColumn(Landroidx/compose/ui/Modifier;Landroidx/tv/foundation/lazy/list/TvLazyListState;Landroidx/compose/foundation/layout/PaddingValuesImpl;ZLandroidx/compose/foundation/layout/Arrangement$Vertical;Landroidx/compose/ui/Alignment$Horizontal;ZLandroidx/tv/foundation/PivotOffsets;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V
+PLkotlin/ResultKt;->checkArgument(Z)V
+PLkotlin/ResultKt;->checkNotPrimitive(Ljava/lang/reflect/Type;)V
+PLkotlin/ResultKt;->equals(Ljava/lang/reflect/Type;Ljava/lang/reflect/Type;)Z
+PLkotlin/ResultKt;->getFilterResult(Ljava/util/List;)V
+PLkotlin/ResultKt;->getGenericSupertype(Ljava/lang/reflect/Type;Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/reflect/Type;
+PLkotlin/ResultKt;->getSupertype(Ljava/lang/reflect/Type;Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/reflect/Type;
+PLkotlin/ResultKt;->resolve(Ljava/lang/reflect/Type;Ljava/lang/Class;Ljava/lang/reflect/Type;Ljava/util/HashMap;)Ljava/lang/reflect/Type;
+PLkotlin/ResultKt;->write(Lcom/google/gson/JsonElement;Lcom/google/gson/stream/JsonWriter;)V
+PLkotlin/TuplesKt;-><init>(I)V
+PLkotlin/TuplesKt;-><init>(Ljava/lang/Object;)V
+PLkotlin/TuplesKt;->access$removeEntryAtIndex([Ljava/lang/Object;I)[Ljava/lang/Object;
+PLkotlin/TuplesKt;->asMutableCollection(Ljava/util/LinkedHashSet;)Ljava/util/Collection;
+PLkotlin/TuplesKt;->closeFinally(Ljava/io/Closeable;Ljava/lang/Throwable;)V
+PLkotlin/TuplesKt;->writeProfile(Landroid/content/Context;Landroidx/profileinstaller/ProfileInstaller$$ExternalSyntheticLambda1;Landroidx/profileinstaller/ProfileInstaller$DiagnosticsCallback;Z)V
+PLkotlin/ULong$Companion;->checkPositionIndex$kotlin_stdlib(II)V
+PLkotlin/ULong$Companion;->onResultReceived(ILjava/lang/Object;)V
+PLkotlin/collections/CollectionsKt___CollectionsKt;->minus(Ljava/util/List;Lkotlin/Function;)Ljava/util/ArrayList;
+PLkotlin/coroutines/jvm/internal/BaseContinuationImpl;->releaseIntercepted()V
+PLkotlin/coroutines/jvm/internal/RestrictedContinuationImpl;-><init>(Lkotlin/coroutines/Continuation;)V
+PLkotlin/coroutines/jvm/internal/RestrictedSuspendLambda;-><init>(Lkotlin/coroutines/Continuation;)V
+PLkotlin/sequences/SequenceBuilderIterator;-><init>()V
+PLkotlin/sequences/SequenceBuilderIterator;->getContext()Lkotlin/coroutines/CoroutineContext;
+PLkotlin/sequences/SequenceBuilderIterator;->hasNext()Z
+PLkotlin/sequences/SequenceBuilderIterator;->next()Ljava/lang/Object;
+PLkotlin/sequences/SequenceBuilderIterator;->resumeWith(Ljava/lang/Object;)V
+PLkotlin/sequences/SequenceBuilderIterator;->yield(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V
+PLkotlin/text/Charsets;-><clinit>()V
+PLkotlinx/coroutines/AbstractCoroutine;->cancellationExceptionMessage()Ljava/lang/String;
+PLkotlinx/coroutines/InvokeOnCompletion;->invoke(Ljava/lang/Throwable;)V
+PLkotlinx/coroutines/JobCancellationException;-><init>(Ljava/lang/String;Ljava/lang/Throwable;Lkotlinx/coroutines/Job;)V
+PLkotlinx/coroutines/JobCancellationException;->equals(Ljava/lang/Object;)Z
+PLkotlinx/coroutines/JobCancellationException;->fillInStackTrace()Ljava/lang/Throwable;
+PLkotlinx/coroutines/JobSupport$Finishing;->isActive()Z
+PLkotlinx/coroutines/JobSupport;->cancellationExceptionMessage()Ljava/lang/String;
+PLkotlinx/coroutines/UndispatchedCoroutine;->afterResume(Ljava/lang/Object;)V
+PLkotlinx/coroutines/flow/FlowKt__ReduceKt$first$3;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
+PLkotlinx/coroutines/flow/SharedFlowSlot;->freeLocked(Lkotlinx/coroutines/flow/internal/AbstractSharedFlow;)[Lkotlin/coroutines/Continuation;
+PLkotlinx/coroutines/flow/SharingConfig;->clear()V
+PLkotlinx/coroutines/flow/StateFlowSlot;->freeLocked(Lkotlinx/coroutines/flow/internal/AbstractSharedFlow;)[Lkotlin/coroutines/Continuation;
+PLkotlinx/coroutines/flow/internal/AbstractSharedFlow;->freeSlot(Lkotlinx/coroutines/flow/internal/AbstractSharedFlowSlot;)V
+PLkotlinx/coroutines/internal/DispatchedContinuation;->cancelCompletedResult$kotlinx_coroutines_core(Ljava/lang/Object;Ljava/util/concurrent/CancellationException;)V
+PLokhttp3/MediaType;->access$locationOf(Ljava/util/ArrayList;II)I
diff --git a/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/NavigationDrawer.kt b/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/NavigationDrawer.kt
index 22cb95a..c9d1f31 100644
--- a/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/NavigationDrawer.kt
+++ b/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/NavigationDrawer.kt
@@ -16,9 +16,7 @@
 
 package androidx.tv.integration.playground
 
-import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -26,38 +24,30 @@
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.selection.selectableGroup
-import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.KeyboardArrowLeft
-import androidx.compose.material.icons.filled.KeyboardArrowRight
+import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
+import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
+import androidx.compose.material.icons.filled.Settings
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.semantics.selected
-import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.zIndex
-import androidx.tv.material3.DrawerValue
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.Icon
 import androidx.tv.material3.NavigationDrawer
+import androidx.tv.material3.NavigationDrawerItem
+import androidx.tv.material3.NavigationDrawerItemDefaults
+import androidx.tv.material3.NavigationDrawerScope
 import androidx.tv.material3.Text
 
 @OptIn(ExperimentalTvMaterial3Api::class)
@@ -68,14 +58,7 @@
     CompositionLocalProvider(LocalLayoutDirection provides direction.value) {
         Row(Modifier.fillMaxSize()) {
             Box(modifier = Modifier.height(400.dp)) {
-                NavigationDrawer(
-                    drawerContent = { drawerValue ->
-                        Sidebar(
-                            drawerValue = drawerValue,
-                            direction = direction,
-                        )
-                    }
-                ) {
+                NavigationDrawer(drawerContent = { Sidebar(direction = direction) }) {
                     CommonBackground()
                 }
             }
@@ -92,12 +75,7 @@
         Row(Modifier.fillMaxSize()) {
             Box(modifier = Modifier.height(400.dp)) {
                 androidx.tv.material3.ModalNavigationDrawer(
-                    drawerContent = { drawerValue ->
-                        Sidebar(
-                            drawerValue = drawerValue,
-                            direction = direction,
-                        )
-                    }
+                    drawerContent = { Sidebar(direction = direction) }
                 ) {
                     CommonBackground()
                 }
@@ -115,10 +93,7 @@
 
 @OptIn(ExperimentalTvMaterial3Api::class)
 @Composable
-private fun Sidebar(
-    drawerValue: DrawerValue,
-    direction: MutableState<LayoutDirection>,
-) {
+private fun NavigationDrawerScope.Sidebar(direction: MutableState<LayoutDirection>) {
     val selectedIndex = remember { mutableStateOf(0) }
 
     LaunchedEffect(selectedIndex.value) {
@@ -132,83 +107,52 @@
         modifier = Modifier
             .fillMaxHeight()
             .background(pageColor)
+            .padding(12.dp)
             .selectableGroup(),
-        horizontalAlignment = Alignment.CenterHorizontally,
+        horizontalAlignment = Alignment.Start,
         verticalArrangement = Arrangement.spacedBy(10.dp)
     ) {
-        @Suppress("DEPRECATION")
-        NavigationItem(
-            imageVector = Icons.Default.KeyboardArrowRight,
-            text = "LTR",
-            drawerValue = drawerValue,
-            selectedIndex = selectedIndex,
-            index = 0
-        )
-        @Suppress("DEPRECATION")
-        NavigationItem(
-            imageVector = Icons.Default.KeyboardArrowLeft,
-            text = "RTL",
-            drawerValue = drawerValue,
-            selectedIndex = selectedIndex,
-            index = 1
-        )
-    }
-}
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-@Composable
-private fun NavigationItem(
-    imageVector: ImageVector,
-    text: String,
-    drawerValue: DrawerValue,
-    selectedIndex: MutableState<Int>,
-    index: Int,
-    modifier: Modifier = Modifier,
-) {
-    var isFocused by remember { mutableStateOf(false) }
-
-    Box(
-        modifier = modifier
-            .clip(RoundedCornerShape(10.dp))
-            .onFocusChanged { isFocused = it.isFocused }
-            .background(if (isFocused) Color.White else Color.Transparent)
-            .semantics(mergeDescendants = true) {
-                selected = selectedIndex.value == index
-            }
-            .clickable {
-                selectedIndex.value = index
-            }
-    ) {
-        Box(modifier = Modifier.padding(10.dp)) {
-            Row(
-                verticalAlignment = Alignment.CenterVertically,
-                horizontalArrangement = Arrangement.spacedBy(5.dp),
-            ) {
+        NavigationDrawerItem(
+            selected = true,
+            onClick = { },
+            leadingContent = {
                 Icon(
-                    imageVector = imageVector,
-                    tint = if (isFocused) pageColor else Color.White,
+                    imageVector = Icons.Default.Settings,
                     contentDescription = null,
                 )
-                AnimatedVisibility(visible = drawerValue == DrawerValue.Open) {
-                    Text(
-                        text = text,
-                        modifier = Modifier,
-                        softWrap = false,
-                        color = if (isFocused) pageColor else Color.White,
-                    )
-                }
+            },
+            supportingContent = {
+                Text("Switch account")
+            },
+            trailingContent = {
+                NavigationDrawerItemDefaults.TrailingBadge("NEW")
             }
-            if (selectedIndex.value == index) {
-                Box(
-                    modifier = Modifier
-                        .width(10.dp)
-                        .height(3.dp)
-                        .offset(y = 5.dp)
-                        .align(Alignment.BottomCenter)
-                        .background(Color.Red)
-                        .zIndex(10f)
+        ) {
+            Text(text = "Hi there")
+        }
+        NavigationDrawerItem(
+            selected = selectedIndex.value == 0,
+            onClick = { selectedIndex.value = 0 },
+            leadingContent = {
+                Icon(
+                    imageVector = Icons.AutoMirrored.Default.KeyboardArrowRight,
+                    contentDescription = null,
                 )
-            }
+            },
+        ) {
+            Text(text = "Left to right")
+        }
+        NavigationDrawerItem(
+            selected = selectedIndex.value == 1,
+            onClick = { selectedIndex.value = 1 },
+            leadingContent = {
+                Icon(
+                    imageVector = Icons.AutoMirrored.Default.KeyboardArrowLeft,
+                    contentDescription = null,
+                )
+            },
+        ) {
+            Text(text = "Right to left")
         }
     }
 }
diff --git a/tv/integration-tests/presentation/build.gradle b/tv/integration-tests/presentation/build.gradle
index 5a9de93..1b3e68c 100644
--- a/tv/integration-tests/presentation/build.gradle
+++ b/tv/integration-tests/presentation/build.gradle
@@ -32,7 +32,8 @@
     implementation(project(":compose:material3:material3"))
     implementation(project(":navigation:navigation-runtime"))
     implementation(project(":profileinstaller:profileinstaller"))
-    implementation "androidx.compose.material:material-icons-extended:1.3.1"
+    implementation(project(":compose:material:material-icons-core"))
+    implementation(project(":compose:material:material-icons-extended"))
 
 //    implementation 'io.coil-kt:coil-compose:2.2.2'
 //    implementation 'com.google.code.gson:gson:2.8.9'
diff --git a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/FeaturedCarousel.kt b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/FeaturedCarousel.kt
index 44d35f9..22f0edd 100644
--- a/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/FeaturedCarousel.kt
+++ b/tv/integration-tests/presentation/src/main/java/androidx/tv/integration/presentation/FeaturedCarousel.kt
@@ -31,7 +31,7 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
 import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.ArrowRight
+import androidx.compose.material.icons.automirrored.outlined.ArrowForward
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -86,7 +86,7 @@
                 @Suppress("DEPRECATION")
                 AppButton(
                     text = "Watch on YouTube",
-                    icon = Icons.Outlined.ArrowRight,
+                    icon = Icons.AutoMirrored.Outlined.ArrowForward,
                 )
             },
         )
diff --git a/tv/onboarding.md b/tv/onboarding.md
index 32d68cf..70a6c9d 100644
--- a/tv/onboarding.md
+++ b/tv/onboarding.md
@@ -1,51 +1,64 @@
 # Onboarding to Compose for TV libraries
 
-## Getting Started
+## Learn about Jetpack Compose
+1. The [Compose landing page][compose-landing-page] provides an overview of Compose and its features.
+2. The [Compose Quick Tutorial][compose-quick-tutorial] walks you through the basics of Compose using code examples.
+3. The [Compose Course][compose-course] is a more comprehensive guide to Compose, covering topics such as layout, animations, and state management.
 
-1. See what Google suggests are [good design patterns][good-design-patterns]
-   and [offered components][tv-components]
-2. Consult the documentation for information on the packages and various components that are
-   offered.
-    * [tv-foundation][tv-foundation]
-    * [tv-foundation-lazy-list][tv-foundation-lazy-list]
-    * [tv-foundation-lazy-grid][tv-foundation-lazy-grid]
-    * [tv-material][tv-material]
-3. Read documentation and examples on [developer.android.com][dac]
-4. Read up on the [codelabs][codelabs]
-5. Ensure that you are on the latest version of [Compose for TV libraries][compose-for-tv-libraries]
+
+## Learn about Compose for TV
+1. Explore the [available components][tv-components] and the [design patterns][good-design-patterns] that Google recommends.
+2. Consult the documentation for information on the packages and various components available.
+   * [tv-foundation][tv-foundation]
+   * [tv-foundation-lazy-list][tv-foundation-lazy-list]
+   * [tv-foundation-lazy-grid][tv-foundation-lazy-grid]
+   * [tv-material][tv-material]
+3. Refer to the documentation and examples on [developer.android.com][dac].
+4. Get up to speed with the [codelabs][codelabs].
+5. Find the sample app on [GitHub][github-sample-app].
+
+## If you run into issues
+1. Make sure that you are using the most recent version of [Compose for TV libraries][compose-for-tv-libraries]
    and Jetpack compose.
-6. Read the FAQs below
-7. Check with the community
-    * [stack overflow][stackoverflow]
-    * slack (TBD)
-    * discord (TBD)
-8. Check if there is a bug already reported on [issue-tracker][issue-tracker]
-9. [File a bug on issue-tracker][issue-tracker-file-a-bug]
-10. Contact a Developer Relations partner from Google who can involve someone from the engineering
-    team as necessary
+2. Read the FAQs below.
+3. Check with the community over on  [stack overflow][stackoverflow].
+4. Check if there is a bug already reported on [issue-tracker][issue-tracker].
+5. File a bug on [issue-tracker][issue-tracker-file-a-bug].
+6. Reach out to a Google Developer Relations partner who can, if necessary, bring in someone from the engineering team.
 
 ## FAQs
 
-1. How can I improve the performance of my app written using tv-compose?
-    * Any [performance improvements][improve-performance] suggested for a Compose app would
-      generally apply to apps built with Compose for TV libraries too.
-    * Use [baseline profiles][baseline-profiles] as recommended
-      in [Jetpack Compose Performance guide][jetpack-compose-performance].
-      Watch [Making apps blazing fast with Baseline Profiles][making-apps-blazing-fast-with-baseline-profiles]
-    * Checkout [Interpreting Compose Compiler Metrics][interpreting-compose-compiler-metrics].
-2. My app is crashing!
-    * Ensure that you are on the latest alpha version
-      of [Compose for TV libraries][compose-for-tv-libraries] and Jetpack Compose
-    * Check if there is a bug already reported on [issue-tracker][issue-tracker]
-    * [File a bug on issue-tracker][issue-tracker-file-a-bug]
-3. The Navigation drawer is pushing my content aside. I don’t like it.
+1. ### How can I improve the performance of my app written using tv-compose?
+   * [Performance improvements][improve-performance] suggested for a Compose app would typically apply to apps built with Compose for TV libraries as well.
+   * Use [baseline profiles][baseline-profiles] as recommended
+     in [Jetpack Compose Performance guide][jetpack-compose-performance].
+     Watch [Making apps blazing fast with Baseline Profiles][making-apps-blazing-fast-with-baseline-profiles].
+   * Check out [Interpreting Compose Compiler Metrics][interpreting-compose-compiler-metrics].
+2. ### My app is crashing!
+   * Ensure that you are on the latest version.
+     of [Compose for TV libraries][compose-for-tv-libraries] and Jetpack Compose
+   * Check if there is a bug already reported on [issue-tracker][issue-tracker].
+   * [File a bug on issue-tracker][issue-tracker-file-a-bug].
+3. ### The Navigation drawer is pushing my content aside. I don’t like it.
    Consider using a [Modal Navigation Drawer][modal-navigation-drawer] provided
-   in [Compose for TV library][compose-for-tv-modal-navigation-drawer]
+   in [Compose for TV library][compose-for-tv-modal-navigation-drawer].
+4. ### Sideloading baseline profiles to test performance, without releasing the app.
+   Refer to the steps for [applying baseline profiles][tv-samples-baseline-profiles] in the
+   Jetstream sample app.
+
+
+[compose-landing-page]: https://developer.android.com/jetpack/compose
+
+[compose-quick-tutorial]: https://developer.android.com/jetpack/compose/tutorial
+
+[compose-course]: https://developer.android.com/courses/jetpack-compose/course
 
 [good-design-patterns]: https://developer.android.com/design/ui/tv
 
 [dac]: https://developer.android.com/training/tv/playback/compose
 
+[github-sample-app]: https://github.com/android/tv-samples/tree/main/JetStreamCompose
+
 [modal-navigation-drawer]: https://m3.material.io/components/navigation-drawer/overview#15a3aa10-1be4-4be4-8370-36a1779f65e5
 
 [compose-for-tv-modal-navigation-drawer]: https://developer.android.com/reference/kotlin/androidx/tv/material3/package-summary#ModalNavigationDrawer(kotlin.Function1,androidx.compose.ui.Modifier,androidx.tv.material3.DrawerState,androidx.compose.ui.graphics.Color,kotlin.Function0)
@@ -79,3 +92,5 @@
 [tv-foundation-lazy-grid]: https://developer.android.com/reference/kotlin/androidx/tv/foundation/lazy/grid/package-summary
 
 [tv-material]: https://developer.android.com/reference/kotlin/androidx/tv/material3/package-summary
+
+[tv-samples-baseline-profiles]: https://github.com/android/tv-samples/blob/main/JetStreamCompose/baseline-profiles.md
\ No newline at end of file
diff --git a/tv/samples/src/main/java/androidx/tv/samples/NavigationDrawerSamples.kt b/tv/samples/src/main/java/androidx/tv/samples/NavigationDrawerSamples.kt
index dc061ec..e96c9d9 100644
--- a/tv/samples/src/main/java/androidx/tv/samples/NavigationDrawerSamples.kt
+++ b/tv/samples/src/main/java/androidx/tv/samples/NavigationDrawerSamples.kt
@@ -17,62 +17,79 @@
 package androidx.tv.samples
 
 import androidx.annotation.Sampled
-import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.foundation.background
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.Settings
 import androidx.compose.material3.Button
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
-import androidx.tv.material3.DrawerValue
 import androidx.tv.material3.ExperimentalTvMaterial3Api
+import androidx.tv.material3.Icon
 import androidx.tv.material3.ModalNavigationDrawer
 import androidx.tv.material3.NavigationDrawer
+import androidx.tv.material3.NavigationDrawerItem
 import androidx.tv.material3.Text
 
 @OptIn(ExperimentalTvMaterial3Api::class)
 @Sampled
 @Composable
 fun SampleNavigationDrawer() {
-    val navigationRow: @Composable (drawerValue: DrawerValue, color: Color, text: String) -> Unit =
-        { drawerValue, color, text ->
-            Row(Modifier.padding(10.dp).focusable()) {
-                Box(Modifier.size(50.dp).background(color).padding(end = 20.dp))
-                AnimatedVisibility(visible = drawerValue == DrawerValue.Open) {
-                    Text(
-                        text = text,
-                        softWrap = false,
-                        modifier = Modifier.padding(15.dp).width(50.dp),
-                        textAlign = TextAlign.Center
-                    )
-                }
-            }
-        }
+    var selectedIndex by remember { mutableIntStateOf(0) }
+
+    val items = listOf(
+        "Home" to Icons.Default.Home,
+        "Settings" to Icons.Default.Settings,
+        "Favourites" to Icons.Default.Favorite,
+    )
 
     NavigationDrawer(
         drawerContent = {
-            Column(Modifier.background(Color.Gray).fillMaxHeight()) {
-                navigationRow(it, Color.Red, "Red")
-                navigationRow(it, Color.Blue, "Blue")
-                navigationRow(it, Color.Yellow, "Yellow")
+            Column(
+                Modifier
+                    .background(Color.Gray)
+                    .fillMaxHeight()
+                    .padding(12.dp)
+                    .selectableGroup(),
+                horizontalAlignment = Alignment.Start,
+                verticalArrangement = Arrangement.spacedBy(10.dp)
+            ) {
+                items.forEachIndexed { index, item ->
+                    val (text, icon) = item
+
+                    NavigationDrawerItem(
+                        selected = selectedIndex == index,
+                        onClick = { selectedIndex = index },
+                        leadingContent = {
+                            Icon(
+                                imageVector = icon,
+                                contentDescription = null,
+                            )
+                        }
+                    ) {
+                        Text(text)
+                    }
+                }
             }
         }
     ) {
-        Button(modifier = Modifier
-            .height(100.dp)
-            .fillMaxWidth(), onClick = {}) {
+        Button(modifier = Modifier.height(100.dp).fillMaxWidth(), onClick = {}) {
             Text("BUTTON")
         }
     }
@@ -82,33 +99,45 @@
 @Sampled
 @Composable
 fun SampleModalNavigationDrawerWithSolidScrim() {
-    val navigationRow: @Composable (drawerValue: DrawerValue, color: Color, text: String) -> Unit =
-        { drawerValue, color, text ->
-            Row(Modifier.padding(10.dp).focusable()) {
-                Box(Modifier.size(50.dp).background(color).padding(end = 20.dp))
-                AnimatedVisibility(visible = drawerValue == DrawerValue.Open) {
-                    Text(
-                        text = text,
-                        softWrap = false,
-                        modifier = Modifier.padding(15.dp).width(50.dp),
-                        textAlign = TextAlign.Center
-                    )
-                }
-            }
-        }
+    var selectedIndex by remember { mutableIntStateOf(0) }
+
+    val items = listOf(
+        "Home" to Icons.Default.Home,
+        "Settings" to Icons.Default.Settings,
+        "Favourites" to Icons.Default.Favorite,
+    )
 
     ModalNavigationDrawer(
         drawerContent = {
-            Column(Modifier.background(Color.Gray).fillMaxHeight()) {
-                navigationRow(it, Color.Red, "Red")
-                navigationRow(it, Color.Blue, "Blue")
-                navigationRow(it, Color.Yellow, "Yellow")
+            Column(
+                Modifier
+                    .background(Color.Gray)
+                    .fillMaxHeight()
+                    .padding(12.dp)
+                    .selectableGroup(),
+                horizontalAlignment = Alignment.Start,
+                verticalArrangement = Arrangement.spacedBy(10.dp)
+            ) {
+                items.forEachIndexed { index, item ->
+                    val (text, icon) = item
+
+                    NavigationDrawerItem(
+                        selected = selectedIndex == index,
+                        onClick = { selectedIndex = index },
+                        leadingContent = {
+                            Icon(
+                                imageVector = icon,
+                                contentDescription = null,
+                            )
+                        }
+                    ) {
+                        Text(text)
+                    }
+                }
             }
         }
     ) {
-        Button(modifier = Modifier
-            .height(100.dp)
-            .fillMaxWidth(), onClick = {}) {
+        Button(modifier = Modifier.height(100.dp).fillMaxWidth(), onClick = {}) {
             Text("BUTTON")
         }
     }
@@ -118,34 +147,46 @@
 @Sampled
 @Composable
 fun SampleModalNavigationDrawerWithGradientScrim() {
-    val navigationRow: @Composable (drawerValue: DrawerValue, color: Color, text: String) -> Unit =
-        { drawerValue, color, text ->
-            Row(Modifier.padding(10.dp).focusable()) {
-                Box(Modifier.size(50.dp).background(color).padding(end = 20.dp))
-                AnimatedVisibility(visible = drawerValue == DrawerValue.Open) {
-                    Text(
-                        text = text,
-                        softWrap = false,
-                        modifier = Modifier.padding(15.dp).width(50.dp),
-                        textAlign = TextAlign.Center
-                    )
-                }
-            }
-        }
+    var selectedIndex by remember { mutableIntStateOf(0) }
 
-    androidx.tv.material3.ModalNavigationDrawer(
+    val items = listOf(
+        "Home" to Icons.Default.Home,
+        "Settings" to Icons.Default.Settings,
+        "Favourites" to Icons.Default.Favorite,
+    )
+
+    ModalNavigationDrawer(
         drawerContent = {
-            Column(Modifier.fillMaxHeight()) {
-                navigationRow(it, Color.Red, "Red")
-                navigationRow(it, Color.Blue, "Blue")
-                navigationRow(it, Color.Yellow, "Yellow")
+            Column(
+                Modifier
+                    .background(Color.Gray)
+                    .fillMaxHeight()
+                    .padding(12.dp)
+                    .selectableGroup(),
+                horizontalAlignment = Alignment.Start,
+                verticalArrangement = Arrangement.spacedBy(10.dp)
+            ) {
+                items.forEachIndexed { index, item ->
+                    val (text, icon) = item
+
+                    NavigationDrawerItem(
+                        selected = selectedIndex == index,
+                        onClick = { selectedIndex = index },
+                        leadingContent = {
+                            Icon(
+                                imageVector = icon,
+                                contentDescription = null,
+                            )
+                        }
+                    ) {
+                        Text(text)
+                    }
+                }
             }
         },
         scrimBrush = Brush.horizontalGradient(listOf(Color.DarkGray, Color.Transparent))
     ) {
-        Button(modifier = Modifier
-            .height(100.dp)
-            .fillMaxWidth(), onClick = {}) {
+        Button(modifier = Modifier.height(100.dp).fillMaxWidth(), onClick = {}) {
             Text("BUTTON")
         }
     }
diff --git a/tv/tv-material/api/api_lint.ignore b/tv/tv-material/api/api_lint.ignore
new file mode 100644
index 0000000..5954166
--- /dev/null
+++ b/tv/tv-material/api/api_lint.ignore
@@ -0,0 +1,4 @@
+// Baseline format: 1.0
+
+GetterSetterNames: field NavigationDrawerScope.doesNavigationDrawerHaveFocus:
+    Invalid name for boolean property `doesNavigationDrawerHaveFocus`. Should start with one of `has`, `can`, `should`, `is`.
\ No newline at end of file
diff --git a/tv/tv-material/api/current.txt b/tv/tv-material/api/current.txt
index 1ea43bf..dcdfda26 100644
--- a/tv/tv-material/api/current.txt
+++ b/tv/tv-material/api/current.txt
@@ -690,6 +690,10 @@
     property public final androidx.tv.material3.Glow selectedGlow;
   }
 
+  public final class NavigationDrawerItemKt {
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void NavigationDrawerItem(androidx.tv.material3.NavigationDrawerScope, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> leadingContent, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingContent, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingContent, optional float tonalElevation, optional androidx.tv.material3.NavigationDrawerItemShape shape, optional androidx.tv.material3.NavigationDrawerItemColors colors, optional androidx.tv.material3.NavigationDrawerItemScale scale, optional androidx.tv.material3.NavigationDrawerItemBorder border, optional androidx.tv.material3.NavigationDrawerItemGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
   @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class NavigationDrawerItemScale {
     ctor public NavigationDrawerItemScale(@FloatRange(from=0.0) float scale, @FloatRange(from=0.0) float focusedScale, @FloatRange(from=0.0) float pressedScale, @FloatRange(from=0.0) float selectedScale, @FloatRange(from=0.0) float disabledScale, @FloatRange(from=0.0) float focusedSelectedScale, @FloatRange(from=0.0) float focusedDisabledScale, @FloatRange(from=0.0) float pressedSelectedScale);
     method public float getDisabledScale();
@@ -737,14 +741,14 @@
   }
 
   public final class NavigationDrawerKt {
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void ModalNavigationDrawer(kotlin.jvm.functions.Function1<? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, optional androidx.compose.ui.graphics.Brush scrimBrush, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void NavigationDrawer(kotlin.jvm.functions.Function1<? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void ModalNavigationDrawer(kotlin.jvm.functions.Function2<? super androidx.tv.material3.NavigationDrawerScope,? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, optional androidx.compose.ui.graphics.Brush scrimBrush, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void NavigationDrawer(kotlin.jvm.functions.Function2<? super androidx.tv.material3.NavigationDrawerScope,? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static androidx.tv.material3.DrawerState rememberDrawerState(androidx.tv.material3.DrawerValue initialValue);
   }
 
   @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public interface NavigationDrawerScope {
-    method public boolean isActivated();
-    property public abstract boolean isActivated;
+    method public boolean getDoesNavigationDrawerHaveFocus();
+    property public abstract boolean doesNavigationDrawerHaveFocus;
   }
 
   @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class NonInteractiveSurfaceColors {
diff --git a/tv/tv-material/api/restricted_current.txt b/tv/tv-material/api/restricted_current.txt
index 1ea43bf..dcdfda26 100644
--- a/tv/tv-material/api/restricted_current.txt
+++ b/tv/tv-material/api/restricted_current.txt
@@ -690,6 +690,10 @@
     property public final androidx.tv.material3.Glow selectedGlow;
   }
 
+  public final class NavigationDrawerItemKt {
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void NavigationDrawerItem(androidx.tv.material3.NavigationDrawerScope, boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> leadingContent, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingContent, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingContent, optional float tonalElevation, optional androidx.tv.material3.NavigationDrawerItemShape shape, optional androidx.tv.material3.NavigationDrawerItemColors colors, optional androidx.tv.material3.NavigationDrawerItemScale scale, optional androidx.tv.material3.NavigationDrawerItemBorder border, optional androidx.tv.material3.NavigationDrawerItemGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
   @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class NavigationDrawerItemScale {
     ctor public NavigationDrawerItemScale(@FloatRange(from=0.0) float scale, @FloatRange(from=0.0) float focusedScale, @FloatRange(from=0.0) float pressedScale, @FloatRange(from=0.0) float selectedScale, @FloatRange(from=0.0) float disabledScale, @FloatRange(from=0.0) float focusedSelectedScale, @FloatRange(from=0.0) float focusedDisabledScale, @FloatRange(from=0.0) float pressedSelectedScale);
     method public float getDisabledScale();
@@ -737,14 +741,14 @@
   }
 
   public final class NavigationDrawerKt {
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void ModalNavigationDrawer(kotlin.jvm.functions.Function1<? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, optional androidx.compose.ui.graphics.Brush scrimBrush, kotlin.jvm.functions.Function0<kotlin.Unit> content);
-    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void NavigationDrawer(kotlin.jvm.functions.Function1<? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void ModalNavigationDrawer(kotlin.jvm.functions.Function2<? super androidx.tv.material3.NavigationDrawerScope,? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, optional androidx.compose.ui.graphics.Brush scrimBrush, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void NavigationDrawer(kotlin.jvm.functions.Function2<? super androidx.tv.material3.NavigationDrawerScope,? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static androidx.tv.material3.DrawerState rememberDrawerState(androidx.tv.material3.DrawerValue initialValue);
   }
 
   @SuppressCompatibility @androidx.tv.material3.ExperimentalTvMaterial3Api public interface NavigationDrawerScope {
-    method public boolean isActivated();
-    property public abstract boolean isActivated;
+    method public boolean getDoesNavigationDrawerHaveFocus();
+    property public abstract boolean doesNavigationDrawerHaveFocus;
   }
 
   @SuppressCompatibility @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class NonInteractiveSurfaceColors {
diff --git a/tv/tv-material/build.gradle b/tv/tv-material/build.gradle
index 2edb6b7..7526ff4 100644
--- a/tv/tv-material/build.gradle
+++ b/tv/tv-material/build.gradle
@@ -43,6 +43,7 @@
     androidTestImplementation(project(":compose:ui:ui-test-junit4"))
     androidTestImplementation(project(":compose:test-utils"))
     androidTestImplementation(project(":test:screenshot:screenshot"))
+    androidTestImplementation(project(":compose:material:material-icons-core"))
     androidTestImplementation(libs.testRunner)
 
     samples(project(":tv:tv-samples"))
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/DenseListItemScreenshotTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/DenseListItemScreenshotTest.kt
index 5c64b89..245ee20 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material3/DenseListItemScreenshotTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/DenseListItemScreenshotTest.kt
@@ -24,8 +24,8 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
 import androidx.compose.material.icons.filled.Favorite
-import androidx.compose.material.icons.filled.KeyboardArrowRight
 import androidx.compose.testutils.assertAgainstGolden
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -371,9 +371,8 @@
                         )
                     },
                     trailingContent = {
-                        @Suppress("DEPRECATION")
                         Icon(
-                            imageVector = Icons.Filled.KeyboardArrowRight,
+                            imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
                             contentDescription = null,
                             modifier = Modifier.size(ListItemDefaults.IconSizeDense)
                         )
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/ListItemScreenshotTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/ListItemScreenshotTest.kt
index 7948021..97c5ecf 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material3/ListItemScreenshotTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/ListItemScreenshotTest.kt
@@ -24,8 +24,8 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
 import androidx.compose.material.icons.filled.Favorite
-import androidx.compose.material.icons.filled.KeyboardArrowRight
 import androidx.compose.testutils.assertAgainstGolden
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -380,9 +380,8 @@
                         )
                     },
                     trailingContent = {
-                        @Suppress("DEPRECATION")
                         Icon(
-                            imageVector = Icons.Filled.KeyboardArrowRight,
+                            imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
                             contentDescription = null,
                             modifier = Modifier.size(ListItemDefaults.IconSize)
                         )
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/NavigationDrawerItemScreenshotTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/NavigationDrawerItemScreenshotTest.kt
new file mode 100644
index 0000000..e98dd07
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/NavigationDrawerItemScreenshotTest.kt
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.tv.material3
+
+import android.os.Build
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onChild
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@OptIn(ExperimentalTvMaterial3Api::class)
+class NavigationDrawerItemScreenshotTest(private val scheme: ColorSchemeWrapper) {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val screenshotRule = AndroidXScreenshotTestRule(TV_GOLDEN_MATERIAL3)
+
+    val wrapperModifier = Modifier
+        .testTag(NavigationDrawerItemWrapperTag)
+        .background(scheme.colorScheme.surface)
+        .padding(20.dp)
+
+    @Test
+    fun navigationDrawerItem_customColor() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = false,
+                    onClick = {},
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+                        )
+                    },
+                    colors = NavigationDrawerItemDefaults.colors(containerColor = Color.Red)
+                ) {
+                    Text("Favourite")
+                }
+            }
+        }
+
+        assertAgainstGolden("navigationDrawerItem_${scheme.name}_customColor")
+    }
+
+    @Test
+    fun navigationDrawerItem_oneLine() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = false,
+                    onClick = {},
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+                        )
+                    }
+                ) {
+                    Text("Favourite")
+                }
+            }
+        }
+
+        assertAgainstGolden("navigationDrawerItem_${scheme.name}_oneLine")
+    }
+
+    @Test
+    fun navigationDrawerItem_twoLine() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = false,
+                    onClick = {},
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+                        )
+                    },
+                    supportingContent = { Text("You like this") }
+                ) {
+                    Text("Favourite")
+                }
+            }
+        }
+
+        assertAgainstGolden("navigationDrawerItem_${scheme.name}_twoLine")
+    }
+
+    @Test
+    fun navigationDrawerItem_twoLine_focused() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = false,
+                    onClick = {},
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+                        )
+                    },
+                    supportingContent = { Text("You like this") }
+                ) {
+                    Text("Favourite")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(NavigationDrawerItemWrapperTag)
+            .onChild()
+            .requestFocus()
+        rule.waitForIdle()
+
+        assertAgainstGolden("navigationDrawerItem_${scheme.name}_twoLine_focused")
+    }
+
+    @Test
+    fun navigationDrawerItem_twoLine_disabled() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = false,
+                    onClick = {},
+                    enabled = false,
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+                        )
+                    },
+                    supportingContent = { Text("You like this") }
+                ) {
+                    Text("Favourite")
+                }
+            }
+        }
+
+        assertAgainstGolden("navigationDrawerItem_${scheme.name}_twoLine_disabled")
+    }
+
+    @Test
+    fun navigationDrawerItem_twoLine_focusedDisabled() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = false,
+                    onClick = {},
+                    enabled = false,
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+                        )
+                    },
+                    supportingContent = { Text("You like this") }
+                ) {
+                    Text("Favourite")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(NavigationDrawerItemWrapperTag)
+            .onChild()
+            .requestFocus()
+        rule.waitForIdle()
+
+        assertAgainstGolden("navigationDrawerItem_${scheme.name}_twoLine_focusedDisabled")
+    }
+
+    @Test
+    fun navigationDrawerItem_twoLine_selected() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = true,
+                    onClick = {},
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+                        )
+                    },
+                    supportingContent = { Text("You like this") }
+                ) {
+                    Text("Favourite")
+                }
+            }
+        }
+
+        assertAgainstGolden("navigationDrawerItem_${scheme.name}_twoLine_selected")
+    }
+
+    @Test
+    fun navigationDrawerItem_twoLine_focusedSelected() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = true,
+                    onClick = {},
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+                        )
+                    },
+                    supportingContent = { Text("You like this") }
+                ) {
+                    Text("Favourite")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(NavigationDrawerItemWrapperTag)
+            .onChild()
+            .requestFocus()
+        rule.waitForIdle()
+
+        assertAgainstGolden("navigationDrawerItem_${scheme.name}_twoLine_focusedSelected")
+    }
+
+    @Test
+    fun navigationDrawerItem_inactive() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            DrawerScope(false) {
+                NavigationDrawerItem(
+                    selected = false,
+                    onClick = {},
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+                        )
+                    },
+                ) {
+                    Text("Favourite")
+                }
+            }
+        }
+
+        assertAgainstGolden("navigationDrawerItem_${scheme.name}_inactive")
+    }
+
+    @Test
+    fun navigationDrawerItem_inactive_selected() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            DrawerScope(false) {
+                NavigationDrawerItem(
+                    selected = true,
+                    onClick = {},
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+                        )
+                    },
+                ) {
+                    Text("Favourite")
+                }
+            }
+        }
+
+        assertAgainstGolden("navigationDrawerItem_${scheme.name}_inactive_selected")
+    }
+
+    @Test
+    fun navigationDrawerItem_twoLine_withTrailingContent() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = false,
+                    onClick = {},
+                    leadingContent = {
+                        Icon(
+                            imageVector = Icons.Filled.Favorite,
+                            contentDescription = null,
+                            modifier = Modifier.size(NavigationDrawerItemDefaults.IconSize)
+                        )
+                    },
+                    supportingContent = { Text("You like this") },
+                    trailingContent = {
+                        NavigationDrawerItemDefaults.TrailingBadge("NEW")
+                    }
+                ) {
+                    Text("Favourite")
+                }
+            }
+        }
+
+        assertAgainstGolden("navigationDrawerItem_${scheme.name}_twoLine_withTrailingContent")
+    }
+
+    private fun assertAgainstGolden(goldenName: String) {
+        rule.onNodeWithTag(NavigationDrawerItemWrapperTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, goldenName)
+    }
+
+    // Provide the ColorScheme and their name parameter in a ColorSchemeWrapper.
+    // This makes sure that the default method name and the initial Scuba image generated
+    // name is as expected.
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun parameters() = arrayOf(
+            ColorSchemeWrapper("lightTheme", lightColorScheme()),
+            ColorSchemeWrapper("darkTheme", darkColorScheme()),
+        )
+    }
+
+    class ColorSchemeWrapper constructor(val name: String, val colorScheme: ColorScheme) {
+        override fun toString(): String {
+            return name
+        }
+    }
+
+    @Composable
+    private fun DrawerScope(
+        doesNavigationDrawerHaveFocus: Boolean = true,
+        content: @Composable NavigationDrawerScope.() -> Unit
+    ) {
+        Box(wrapperModifier) {
+            NavigationDrawerScopeImpl(doesNavigationDrawerHaveFocus).apply {
+                content()
+            }
+        }
+    }
+}
+
+private const val NavigationDrawerItemWrapperTag = "navigationDrawerItem_wrapper"
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/NavigationDrawerItemTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/NavigationDrawerItemTest.kt
new file mode 100644
index 0000000..74d9ee4
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/NavigationDrawerItemTest.kt
@@ -0,0 +1,443 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.tv.material3
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertHasClickAction
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsEqualTo
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.width
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(
+    ExperimentalTestApi::class,
+    ExperimentalTvMaterial3Api::class
+)
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class NavigationDrawerItemTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun navigationDrawerItem_findByTagAndClick() {
+        var counter = 0
+        val onClick: () -> Unit = { ++counter }
+
+        rule.setContent {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = false,
+                    onClick = onClick,
+                    leadingContent = { },
+                    modifier = Modifier.testTag(NavigationDrawerItemTag),
+                ) {
+                    Text(text = "Test Text")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(NavigationDrawerItemTag)
+            .requestFocus()
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+        rule.runOnIdle {
+            Truth.assertThat(counter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun navigationDrawerItem_clickIsIndependentBetweenItems() {
+        var openItemClickCounter = 0
+        val openItemOnClick: () -> Unit = { ++openItemClickCounter }
+        val openItemTag = "OpenItem"
+
+        var closeItemClickCounter = 0
+        val closeItemOnClick: () -> Unit = { ++closeItemClickCounter }
+        val closeItemTag = "CloseItem"
+
+        rule.setContent {
+            DrawerScope {
+                Column {
+                    NavigationDrawerItem(
+                        selected = false,
+                        onClick = openItemOnClick,
+                        leadingContent = { },
+                        modifier = Modifier.testTag(openItemTag),
+                    ) {
+                        Text(text = "Test Text")
+                    }
+                    NavigationDrawerItem(
+                        selected = false,
+                        onClick = closeItemOnClick,
+                        leadingContent = { },
+                        modifier = Modifier.testTag(closeItemTag),
+                    ) {
+                        Text(text = "Test Text")
+                    }
+                }
+            }
+        }
+
+        rule.onNodeWithTag(openItemTag)
+            .requestFocus()
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        rule.runOnIdle {
+            Truth.assertThat(openItemClickCounter).isEqualTo(1)
+            Truth.assertThat(closeItemClickCounter).isEqualTo(0)
+        }
+
+        rule.onNodeWithTag(closeItemTag)
+            .requestFocus()
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        rule.runOnIdle {
+            Truth.assertThat(openItemClickCounter).isEqualTo(1)
+            Truth.assertThat(closeItemClickCounter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun navigationDrawerItem_longClickAction() {
+        var counter = 0
+        val onLongClick: () -> Unit = { ++counter }
+
+        rule.setContent {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = false,
+                    onClick = { },
+                    onLongClick = onLongClick,
+                    leadingContent = { },
+                    modifier = Modifier.testTag(NavigationDrawerItemTag),
+                ) {
+                    Text(text = "Test Text")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(NavigationDrawerItemTag)
+            .requestFocus()
+            .performLongKeyPress(rule, Key.DirectionCenter)
+        rule.runOnIdle {
+            Truth.assertThat(counter).isEqualTo(1)
+        }
+
+        rule.onNodeWithTag(NavigationDrawerItemTag)
+            .requestFocus()
+            .performLongKeyPress(rule, Key.DirectionCenter, count = 2)
+        rule.runOnIdle {
+            Truth.assertThat(counter).isEqualTo(3)
+        }
+    }
+
+    @Test
+    fun navigationDrawerItem_findByTagAndStateChangeCheck() {
+        var checkedState by mutableStateOf(true)
+        val onClick: () -> Unit = { checkedState = !checkedState }
+
+        rule.setContent {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = checkedState,
+                    onClick = onClick,
+                    leadingContent = { },
+                    modifier = Modifier.testTag(NavigationDrawerItemTag),
+                ) {
+                    Text(text = "Test Text")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(NavigationDrawerItemTag)
+            .requestFocus()
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+        rule.runOnIdle {
+            Truth.assertThat(!checkedState)
+        }
+    }
+
+    @Test
+    fun navigationDrawerItem_trailingContentPadding() {
+        val testTrailingContentTag = "TrailingIconTag"
+
+        rule.setContent {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = false,
+                    onClick = {},
+                    trailingContent = {
+                        Box(
+                            modifier = Modifier
+                                .size(NavigationDrawerItemDefaults.IconSize)
+                                .background(Color.Red)
+                                .testTag(testTrailingContentTag)
+                        )
+                    },
+                    leadingContent = { },
+                    modifier = Modifier
+                        .testTag(NavigationDrawerItemTag)
+                        .border(1.dp, Color.Blue),
+                ) {
+                    Text(
+                        text = "Test Text",
+                        modifier = Modifier
+                            .testTag(NavigationDrawerItemTextTag)
+                            .fillMaxWidth()
+                    )
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        val itemBounds = rule.onNodeWithTag(NavigationDrawerItemTag).getUnclippedBoundsInRoot()
+        val textBounds = rule.onNodeWithTag(
+            NavigationDrawerItemTextTag,
+            useUnmergedTree = true
+        ).getUnclippedBoundsInRoot()
+        val trailingContentBounds = rule
+            .onNodeWithTag(testTrailingContentTag, useUnmergedTree = true)
+            .getUnclippedBoundsInRoot()
+
+        (itemBounds.bottom - trailingContentBounds.bottom).assertIsEqualTo(
+            16.dp,
+            "padding between the bottom of the trailing content and the bottom of the nav " +
+                "drawer item"
+        )
+
+        (itemBounds.right - trailingContentBounds.right).assertIsEqualTo(
+            16.dp,
+            "padding between the end of the trailing content and the end of the nav drawer item"
+        )
+
+        (trailingContentBounds.left - textBounds.right).assertIsEqualTo(
+            8.dp,
+            "padding between the start of the trailing content and the end of the text."
+        )
+    }
+
+    @Test
+    fun navigationDrawerItem_semantics() {
+        var selected by mutableStateOf(false)
+        rule.setContent {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = selected,
+                    onClick = { selected = !selected },
+                    leadingContent = { },
+                    modifier = Modifier.testTag(NavigationDrawerItemTag),
+                ) {
+                    Text(text = "Test Text")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(NavigationDrawerItemTag)
+            .assertHasClickAction()
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.Selected))
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Selected, false))
+            .requestFocus()
+            .assertIsEnabled()
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Selected, true))
+        Truth.assertThat(selected).isEqualTo(true)
+    }
+
+    @Test
+    fun navigationDrawerItem_longClickSemantics() {
+        var selected by mutableStateOf(false)
+        rule.setContent {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = selected,
+                    onClick = {},
+                    onLongClick = { selected = !selected },
+                    leadingContent = { },
+                    modifier = Modifier.testTag(NavigationDrawerItemTag),
+                ) {
+                    Text(text = "Test Text")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(NavigationDrawerItemTag)
+            .assertHasClickAction()
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.OnLongClick))
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.Selected))
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Selected, false))
+            .requestFocus()
+            .assertIsEnabled()
+            .performLongKeyPress(rule, Key.DirectionCenter)
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Selected, true))
+        Truth.assertThat(selected).isEqualTo(true)
+    }
+
+    @Test
+    fun navigationDrawerItem_disabledSemantics() {
+        rule.setContent {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = false,
+                    onClick = {},
+                    enabled = false,
+                    leadingContent = { },
+                    modifier = Modifier.testTag(NavigationDrawerItemTag),
+                ) {
+                    Text(text = "Test Text")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(NavigationDrawerItemTag)
+            .assertIsNotEnabled()
+    }
+
+    @Test
+    fun navigationDrawerItem_canBeDisabled() {
+        rule.setContent {
+            var enabled by remember { mutableStateOf(true) }
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = false,
+                    onClick = { enabled = false },
+                    leadingContent = { },
+                    enabled = enabled,
+                    modifier = Modifier.testTag(NavigationDrawerItemTag),
+                ) {
+                    Text(text = "Test Text")
+                }
+            }
+        }
+        rule.onNodeWithTag(NavigationDrawerItemTag)
+            // Confirm the button starts off enabled, with a click action
+            .assertHasClickAction()
+            .assertIsEnabled()
+            .requestFocus()
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+            // Then confirm it's disabled with click action after clicking it
+            .assertHasClickAction()
+            .assertIsNotEnabled()
+    }
+
+    @Test
+    fun navigationDrawerItem_oneLineHeight() {
+        val expectedHeightNoIcon = 56.dp
+
+        rule.setContent {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = false,
+                    onClick = {},
+                    leadingContent = { },
+                    modifier = Modifier.testTag(NavigationDrawerItemTag),
+                ) {
+                    Text(text = "Test Text")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(NavigationDrawerItemTag).assertHeightIsEqualTo(expectedHeightNoIcon)
+    }
+
+    @Test
+    fun navigationDrawerItem_width() {
+        rule.setContent {
+            DrawerScope {
+                NavigationDrawerItem(
+                    selected = false,
+                    onClick = { },
+                    leadingContent = { },
+                    modifier = Modifier.testTag(NavigationDrawerItemTag),
+                ) {
+                    Text(text = "Test Text")
+                }
+            }
+        }
+        rule.onNodeWithTag(NavigationDrawerItemTag)
+            .assertWidthIsEqualTo(rule.onRoot().getUnclippedBoundsInRoot().width)
+    }
+
+    @Test
+    fun navigationDrawerItem_widthInInactiveState() {
+        rule.setContent {
+            DrawerScope(false) {
+                NavigationDrawerItem(
+                    selected = false,
+                    onClick = { },
+                    leadingContent = { },
+                    modifier = Modifier.testTag(NavigationDrawerItemTag),
+                ) {
+                    Text(text = "Test Text")
+                }
+            }
+        }
+        rule.onNodeWithTag(NavigationDrawerItemTag)
+            .assertWidthIsEqualTo(56.dp)
+    }
+}
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun DrawerScope(
+    isActivated: Boolean = true,
+    content: @Composable NavigationDrawerScope.() -> Unit
+) {
+    Box {
+        NavigationDrawerScopeImpl(isActivated).apply {
+            content()
+        }
+    }
+}
+
+private const val NavigationDrawerItemTag = "NavigationDrawerItem"
+private const val NavigationDrawerItemTextTag = "NavigationDrawerItemText"
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawer.kt b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawer.kt
index 049ddf1..320fe9e 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawer.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawer.kt
@@ -79,7 +79,7 @@
 @ExperimentalTvMaterial3Api
 @Composable
 fun ModalNavigationDrawer(
-    drawerContent: @Composable (DrawerValue) -> Unit,
+    drawerContent: @Composable NavigationDrawerScope.(DrawerValue) -> Unit,
     modifier: Modifier = Modifier,
     drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
     scrimBrush: Brush = SolidColor(LocalColorScheme.current.scrim.copy(alpha = 0.5f)),
@@ -155,7 +155,7 @@
 @ExperimentalTvMaterial3Api
 @Composable
 fun NavigationDrawer(
-    drawerContent: @Composable (DrawerValue) -> Unit,
+    drawerContent: @Composable NavigationDrawerScope.(DrawerValue) -> Unit,
     modifier: Modifier = Modifier,
     drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
     content: @Composable () -> Unit
@@ -237,7 +237,7 @@
     modifier: Modifier = Modifier,
     drawerState: DrawerState = remember { DrawerState() },
     sizeAnimationFinishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null,
-    content: @Composable (DrawerValue) -> Unit
+    content: @Composable NavigationDrawerScope.(DrawerValue) -> Unit
 ) {
     // indicates that the drawer has been set to its initial state and has grabbed focus if
     // necessary. Controls whether focus is used to decide the state of the drawer going forward.
@@ -269,7 +269,11 @@
             }
             .focusGroup()
 
-    Box(modifier = internalModifier) { content.invoke(drawerState.currentValue) }
+    Box(modifier = internalModifier) {
+        NavigationDrawerScopeImpl(drawerState.currentValue == DrawerValue.Open).apply {
+            content(drawerState.currentValue)
+        }
+    }
 }
 
 private const val ClosedDrawerWidth = 80
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItem.kt b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItem.kt
new file mode 100644
index 0000000..cc97ac8
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItem.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.tv.material3
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.unit.Dp
+
+/**
+ * TV Material Design navigation drawer item.
+ *
+ * A [NavigationDrawerItem] represents a destination within drawers, either [NavigationDrawer] or
+ * [ModalNavigationDrawer]
+ *
+ * @sample androidx.tv.samples.SampleNavigationDrawer
+ * @sample androidx.tv.samples.SampleModalNavigationDrawerWithSolidScrim
+ * @sample androidx.tv.samples.SampleModalNavigationDrawerWithGradientScrim
+ *
+ * @param selected defines whether this composable is selected or not
+ * @param onClick called when this composable is clicked
+ * @param leadingContent the leading content of the list item
+ * @param modifier to be applied to the list item
+ * @param enabled controls the enabled state of this composable. When `false`, this component will
+ * not respond to user input, and it will appear visually disabled and disabled to accessibility
+ * services
+ * @param onLongClick called when this composable is long clicked (long-pressed)
+ * @param supportingContent the content displayed below the headline content
+ * @param trailingContent the trailing meta badge or icon
+ * @param tonalElevation the tonal elevation of this composable
+ * @param shape defines the shape of Composable's container in different interaction states
+ * @param colors defines the background and content colors used in the composable
+ * for different interaction states
+ * @param scale defines the size of the composable relative to its original size in
+ * different interaction states
+ * @param border defines a border around the composable in different interaction states
+ * @param glow defines a shadow to be shown behind the composable for different interaction states
+ * @param interactionSource the [MutableInteractionSource] representing the stream of
+ * [Interaction]s for this component. You can create and pass in your own [remember]ed instance
+ * to observe [Interaction]s and customize the appearance / behavior of this composable in different
+ * states
+ * @param content main content of this composable
+ */
+@ExperimentalTvMaterial3Api // TODO (b/263353219): Remove this before launching beta
+@Composable
+fun NavigationDrawerScope.NavigationDrawerItem(
+    selected: Boolean,
+    onClick: () -> Unit,
+    leadingContent: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    onLongClick: (() -> Unit)? = null,
+    supportingContent: (@Composable () -> Unit)? = null,
+    trailingContent: (@Composable () -> Unit)? = null,
+    tonalElevation: Dp = NavigationDrawerItemDefaults.NavigationDrawerItemElevation,
+    shape: NavigationDrawerItemShape = NavigationDrawerItemDefaults.shape(),
+    colors: NavigationDrawerItemColors = NavigationDrawerItemDefaults.colors(),
+    scale: NavigationDrawerItemScale = NavigationDrawerItemScale.None,
+    border: NavigationDrawerItemBorder = NavigationDrawerItemDefaults.border(),
+    glow: NavigationDrawerItemGlow = NavigationDrawerItemDefaults.glow(),
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    content: @Composable () -> Unit,
+) {
+    val animatedWidth by animateDpAsState(
+        targetValue = if (doesNavigationDrawerHaveFocus) {
+            NavigationDrawerItemDefaults.ExpandedDrawerItemWidth
+        } else {
+            NavigationDrawerItemDefaults.CollapsedDrawerItemWidth
+        },
+        label = "NavigationDrawerItem width open/closed state of the drawer item"
+    )
+    val navDrawerItemHeight = if (supportingContent == null) {
+        NavigationDrawerItemDefaults.ContainerHeightOneLine
+    } else {
+        NavigationDrawerItemDefaults.ContainerHeightTwoLine
+    }
+    ListItem(
+        selected = selected,
+        onClick = onClick,
+        headlineContent = {
+            AnimatedVisibility(
+                visible = doesNavigationDrawerHaveFocus,
+                enter = NavigationDrawerItemDefaults.ContentAnimationEnter,
+                exit = NavigationDrawerItemDefaults.ContentAnimationExit,
+            ) {
+                content()
+            }
+        },
+        leadingContent = {
+            Box(Modifier.size(NavigationDrawerItemDefaults.IconSize)) {
+                leadingContent()
+            }
+        },
+        trailingContent = trailingContent?.let {
+            {
+                AnimatedVisibility(
+                    visible = doesNavigationDrawerHaveFocus,
+                    enter = NavigationDrawerItemDefaults.ContentAnimationEnter,
+                    exit = NavigationDrawerItemDefaults.ContentAnimationExit,
+                ) {
+                    it()
+                }
+            }
+        },
+        supportingContent = supportingContent?.let {
+            {
+                AnimatedVisibility(
+                    visible = doesNavigationDrawerHaveFocus,
+                    enter = NavigationDrawerItemDefaults.ContentAnimationEnter,
+                    exit = NavigationDrawerItemDefaults.ContentAnimationExit,
+                ) {
+                    it()
+                }
+            }
+        },
+        modifier = modifier
+            .layout { measurable, constraints ->
+                val width = animatedWidth.roundToPx()
+                val height = navDrawerItemHeight.roundToPx()
+                val placeable = measurable.measure(
+                    constraints.copy(
+                        minWidth = width,
+                        maxWidth = width,
+                        minHeight = height,
+                        maxHeight = height,
+                    )
+                )
+                layout(placeable.width, placeable.height) {
+                    placeable.place(0, 0)
+                }
+            },
+        enabled = enabled,
+        onLongClick = onLongClick,
+        tonalElevation = tonalElevation,
+        shape = shape.toToggleableListItemShape(),
+        colors = colors.toToggleableListItemColors(doesNavigationDrawerHaveFocus),
+        scale = scale.toToggleableListItemScale(),
+        border = border.toToggleableListItemBorder(),
+        glow = glow.toToggleableListItemGlow(),
+        interactionSource = interactionSource,
+    )
+}
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun NavigationDrawerItemShape.toToggleableListItemShape() =
+    ListItemDefaults.shape(
+        shape = shape,
+        focusedShape = focusedShape,
+        pressedShape = pressedShape,
+        selectedShape = selectedShape,
+        disabledShape = disabledShape,
+        focusedSelectedShape = focusedSelectedShape,
+        focusedDisabledShape = focusedDisabledShape,
+        pressedSelectedShape = pressedSelectedShape,
+    )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun NavigationDrawerItemColors.toToggleableListItemColors(
+    doesNavigationDrawerHaveFocus: Boolean
+) =
+    ListItemDefaults.colors(
+        containerColor = containerColor,
+        contentColor = if (doesNavigationDrawerHaveFocus) contentColor else inactiveContentColor,
+        focusedContainerColor = focusedContainerColor,
+        focusedContentColor = focusedContentColor,
+        pressedContainerColor = pressedContainerColor,
+        pressedContentColor = pressedContentColor,
+        selectedContainerColor = selectedContainerColor,
+        selectedContentColor = selectedContentColor,
+        disabledContainerColor = disabledContainerColor,
+        disabledContentColor =
+        if (doesNavigationDrawerHaveFocus) disabledContentColor else disabledInactiveContentColor,
+        focusedSelectedContainerColor = focusedSelectedContainerColor,
+        focusedSelectedContentColor = focusedSelectedContentColor,
+        pressedSelectedContainerColor = pressedSelectedContainerColor,
+        pressedSelectedContentColor = pressedSelectedContentColor,
+    )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun NavigationDrawerItemScale.toToggleableListItemScale() =
+    ListItemDefaults.scale(
+        scale = scale,
+        focusedScale = focusedScale,
+        pressedScale = pressedScale,
+        selectedScale = selectedScale,
+        disabledScale = disabledScale,
+        focusedSelectedScale = focusedSelectedScale,
+        focusedDisabledScale = focusedDisabledScale,
+        pressedSelectedScale = pressedSelectedScale,
+    )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun NavigationDrawerItemBorder.toToggleableListItemBorder() =
+    ListItemDefaults.border(
+        border = border,
+        focusedBorder = focusedBorder,
+        pressedBorder = pressedBorder,
+        selectedBorder = selectedBorder,
+        disabledBorder = disabledBorder,
+        focusedSelectedBorder = focusedSelectedBorder,
+        focusedDisabledBorder = focusedDisabledBorder,
+        pressedSelectedBorder = pressedSelectedBorder,
+    )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun NavigationDrawerItemGlow.toToggleableListItemGlow() =
+    ListItemDefaults.glow(
+        glow = glow,
+        focusedGlow = focusedGlow,
+        pressedGlow = pressedGlow,
+        selectedGlow = selectedGlow,
+        focusedSelectedGlow = focusedSelectedGlow,
+        pressedSelectedGlow = pressedSelectedGlow,
+    )
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItemDefaults.kt b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItemDefaults.kt
index 44de75e..debfd6f 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItemDefaults.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItemDefaults.kt
@@ -38,27 +38,27 @@
 import androidx.tv.material3.tokens.Elevation
 
 /**
- * Contains the default values used by selectable NavigationDrawerItem
+ * Contains the default values used by selectable [NavigationDrawerItem]
  */
 @ExperimentalTvMaterial3Api // TODO (b/263353219): Remove this before launching beta
 object NavigationDrawerItemDefaults {
     /**
-     * The default Icon size used by NavigationDrawerItem
+     * The default Icon size used by [NavigationDrawerItem]
      */
     val IconSize = 24.dp
 
     /**
-     * The size of the NavigationDrawerItem when the drawer is collapsed
+     * The size of the [NavigationDrawerItem] when the drawer is collapsed
      */
     val CollapsedDrawerItemWidth = 56.dp
 
     /**
-     * The size of the NavigationDrawerItem when the drawer is expanded
+     * The size of the [NavigationDrawerItem] when the drawer is expanded
      */
     val ExpandedDrawerItemWidth = 256.dp
 
     /**
-     * The default content padding [PaddingValues] used by NavigationDrawerItem when the drawer
+     * The default content padding [PaddingValues] used by [NavigationDrawerItem] when the drawer
      * is expanded
      */
 
@@ -66,7 +66,7 @@
     val ContainerHeightTwoLine = 64.dp
 
     /**
-     * The default elevation used by NavigationDrawerItem
+     * The default elevation used by [NavigationDrawerItem]
      */
     val NavigationDrawerItemElevation = Elevation.Level0
 
@@ -81,7 +81,7 @@
     val ContentAnimationExit = fadeOut() + slideOut { IntOffset(0, 0) }
 
     /**
-     * Default border used by NavigationDrawerItem
+     * Default border used by [NavigationDrawerItem]
      */
     val DefaultBorder
         @ReadOnlyComposable
@@ -93,28 +93,28 @@
         )
 
     /**
-     * The default container color used by NavigationDrawerItem's trailing badge
+     * The default container color used by [NavigationDrawerItem]'s trailing badge
      */
     val TrailingBadgeContainerColor
         @ReadOnlyComposable
         @Composable get() = MaterialTheme.colorScheme.tertiary
 
     /**
-     * The default text style used by NavigationDrawerItem's trailing badge
+     * The default text style used by [NavigationDrawerItem]'s trailing badge
      */
     val TrailingBadgeTextStyle
         @ReadOnlyComposable
         @Composable get() = MaterialTheme.typography.labelSmall
 
     /**
-     * The default content color used by NavigationDrawerItem's trailing badge
+     * The default content color used by [NavigationDrawerItem]'s trailing badge
      */
     val TrailingBadgeContentColor
         @ReadOnlyComposable
         @Composable get() = MaterialTheme.colorScheme.onTertiary
 
     /**
-     * Creates a trailing badge for NavigationDrawerItem
+     * Creates a trailing badge for [NavigationDrawerItem]
      */
     @Composable
     fun TrailingBadge(
@@ -138,18 +138,18 @@
 
     /**
      * Creates a [NavigationDrawerItemShape] that represents the default container shapes
-     * used in a selectable NavigationDrawerItem
+     * used in a selectable [NavigationDrawerItem]
      *
-     * @param shape the default shape used when the NavigationDrawerItem is enabled
-     * @param focusedShape the shape used when the NavigationDrawerItem is enabled and focused
-     * @param pressedShape the shape used when the NavigationDrawerItem is enabled and pressed
-     * @param selectedShape the shape used when the NavigationDrawerItem is enabled and selected
-     * @param disabledShape the shape used when the NavigationDrawerItem is not enabled
-     * @param focusedSelectedShape the shape used when the NavigationDrawerItem is enabled,
+     * @param shape the default shape used when the [NavigationDrawerItem] is enabled
+     * @param focusedShape the shape used when the [NavigationDrawerItem] is enabled and focused
+     * @param pressedShape the shape used when the [NavigationDrawerItem] is enabled and pressed
+     * @param selectedShape the shape used when the [NavigationDrawerItem] is enabled and selected
+     * @param disabledShape the shape used when the [NavigationDrawerItem] is not enabled
+     * @param focusedSelectedShape the shape used when the [NavigationDrawerItem] is enabled,
      * focused and selected
-     * @param focusedDisabledShape the shape used when the NavigationDrawerItem is not enabled
+     * @param focusedDisabledShape the shape used when the [NavigationDrawerItem] is not enabled
      * and focused
-     * @param pressedSelectedShape the shape used when the NavigationDrawerItem is enabled,
+     * @param pressedSelectedShape the shape used when the [NavigationDrawerItem] is enabled,
      * pressed and selected
      */
     fun shape(
@@ -174,38 +174,38 @@
 
     /**
      * Creates a [NavigationDrawerItemColors] that represents the default container &
-     * content colors used in a selectable NavigationDrawerItem
+     * content colors used in a selectable [NavigationDrawerItem]
      *
-     * @param containerColor the default container color used when the NavigationDrawerItem is
+     * @param containerColor the default container color used when the [NavigationDrawerItem] is
      * enabled
-     * @param contentColor the default content color used when the NavigationDrawerItem is enabled
+     * @param contentColor the default content color used when the [NavigationDrawerItem] is enabled
      * @param inactiveContentColor the content color used when none of the navigation items have
      * focus
-     * @param focusedContainerColor the container color used when the NavigationDrawerItem is
+     * @param focusedContainerColor the container color used when the [NavigationDrawerItem] is
      * enabled and focused
-     * @param focusedContentColor the content color used when the NavigationDrawerItem is enabled
+     * @param focusedContentColor the content color used when the [NavigationDrawerItem] is enabled
      * and focused
-     * @param pressedContainerColor the container color used when the NavigationDrawerItem is
+     * @param pressedContainerColor the container color used when the [NavigationDrawerItem] is
      * enabled and pressed
-     * @param pressedContentColor the content color used when the NavigationDrawerItem is enabled
+     * @param pressedContentColor the content color used when the [NavigationDrawerItem] is enabled
      * and pressed
-     * @param selectedContainerColor the container color used when the NavigationDrawerItem is
+     * @param selectedContainerColor the container color used when the [NavigationDrawerItem] is
      * enabled and selected
-     * @param selectedContentColor the content color used when the NavigationDrawerItem is
+     * @param selectedContentColor the content color used when the [NavigationDrawerItem] is
      * enabled and selected
-     * @param disabledContainerColor the container color used when the NavigationDrawerItem is
+     * @param disabledContainerColor the container color used when the [NavigationDrawerItem] is
      * not enabled
-     * @param disabledContentColor the content color used when the NavigationDrawerItem is not
+     * @param disabledContentColor the content color used when the [NavigationDrawerItem] is not
      * enabled
      * @param disabledInactiveContentColor the content color used when none of the navigation items
      * have focus and this item is disabled
      * @param focusedSelectedContainerColor the container color used when the
      * NavigationDrawerItem is enabled, focused and selected
-     * @param focusedSelectedContentColor the content color used when the NavigationDrawerItem
+     * @param focusedSelectedContentColor the content color used when the [NavigationDrawerItem]
      * is enabled, focused and selected
      * @param pressedSelectedContainerColor the container color used when the
-     * NavigationDrawerItem is enabled, pressed and selected
-     * @param pressedSelectedContentColor the content color used when the NavigationDrawerItem is
+     * [NavigationDrawerItem] is enabled, pressed and selected
+     * @param pressedSelectedContentColor the content color used when the [NavigationDrawerItem] is
      * enabled, pressed and selected
      */
     @ReadOnlyComposable
@@ -249,22 +249,22 @@
 
     /**
      * Creates a [NavigationDrawerItemScale] that represents the default scales used in a
-     * selectable NavigationDrawerItem
+     * selectable [NavigationDrawerItem]
      *
      * scales are used to modify the size of a composable in different [Interaction] states
      * e.g. `1f` (original) in default state, `1.2f` (scaled up) in focused state, `0.8f` (scaled
      * down) in pressed state, etc.
      *
-     * @param scale the scale used when the NavigationDrawerItem is enabled
-     * @param focusedScale the scale used when the NavigationDrawerItem is enabled and focused
-     * @param pressedScale the scale used when the NavigationDrawerItem is enabled and pressed
-     * @param selectedScale the scale used when the NavigationDrawerItem is enabled and selected
-     * @param disabledScale the scale used when the NavigationDrawerItem is not enabled
-     * @param focusedSelectedScale the scale used when the NavigationDrawerItem is enabled,
+     * @param scale the scale used when the [NavigationDrawerItem] is enabled
+     * @param focusedScale the scale used when the [NavigationDrawerItem] is enabled and focused
+     * @param pressedScale the scale used when the [NavigationDrawerItem] is enabled and pressed
+     * @param selectedScale the scale used when the [NavigationDrawerItem] is enabled and selected
+     * @param disabledScale the scale used when the [NavigationDrawerItem] is not enabled
+     * @param focusedSelectedScale the scale used when the [NavigationDrawerItem] is enabled,
      * focused and selected
-     * @param focusedDisabledScale the scale used when the NavigationDrawerItem is not enabled and
+     * @param focusedDisabledScale the scale used when the [NavigationDrawerItem] is not enabled and
      * focused
-     * @param pressedSelectedScale the scale used when the NavigationDrawerItem is enabled,
+     * @param pressedSelectedScale the scale used when the [NavigationDrawerItem] is enabled,
      * pressed and selected
      */
     fun scale(
@@ -289,19 +289,19 @@
 
     /**
      * Creates a [NavigationDrawerItemBorder] that represents the default [Border]s
-     * applied on a selectable NavigationDrawerItem in different [Interaction] states
+     * applied on a selectable [NavigationDrawerItem] in different [Interaction] states
      *
-     * @param border the default [Border] used when the NavigationDrawerItem is enabled
-     * @param focusedBorder the [Border] used when the NavigationDrawerItem is enabled and focused
-     * @param pressedBorder the [Border] used when the NavigationDrawerItem is enabled and pressed
-     * @param selectedBorder the [Border] used when the NavigationDrawerItem is enabled and
+     * @param border the default [Border] used when the [NavigationDrawerItem] is enabled
+     * @param focusedBorder the [Border] used when the [NavigationDrawerItem] is enabled and focused
+     * @param pressedBorder the [Border] used when the [NavigationDrawerItem] is enabled and pressed
+     * @param selectedBorder the [Border] used when the [NavigationDrawerItem] is enabled and
      * selected
-     * @param disabledBorder the [Border] used when the NavigationDrawerItem is not enabled
-     * @param focusedSelectedBorder the [Border] used when the NavigationDrawerItem is enabled,
+     * @param disabledBorder the [Border] used when the [NavigationDrawerItem] is not enabled
+     * @param focusedSelectedBorder the [Border] used when the [NavigationDrawerItem] is enabled,
      * focused and selected
-     * @param focusedDisabledBorder the [Border] used when the NavigationDrawerItem is not
+     * @param focusedDisabledBorder the [Border] used when the [NavigationDrawerItem] is not
      * enabled and focused
-     * @param pressedSelectedBorder the [Border] used when the NavigationDrawerItem is enabled,
+     * @param pressedSelectedBorder the [Border] used when the [NavigationDrawerItem] is enabled,
      * pressed and selected
      */
     @ReadOnlyComposable
@@ -328,16 +328,16 @@
 
     /**
      * Creates a [NavigationDrawerItemGlow] that represents the default [Glow]s used in a
-     * selectable NavigationDrawerItem
+     * selectable [NavigationDrawerItem]
      *
-     * @param glow the [Glow] used when the NavigationDrawerItem is enabled, and has no other
+     * @param glow the [Glow] used when the [NavigationDrawerItem] is enabled, and has no other
      * [Interaction]s
-     * @param focusedGlow the [Glow] used when the NavigationDrawerItem is enabled and focused
-     * @param pressedGlow the [Glow] used when the NavigationDrawerItem is enabled and pressed
-     * @param selectedGlow the [Glow] used when the NavigationDrawerItem is enabled and selected
-     * @param focusedSelectedGlow the [Glow] used when the NavigationDrawerItem is enabled,
+     * @param focusedGlow the [Glow] used when the [NavigationDrawerItem] is enabled and focused
+     * @param pressedGlow the [Glow] used when the [NavigationDrawerItem] is enabled and pressed
+     * @param selectedGlow the [Glow] used when the [NavigationDrawerItem] is enabled and selected
+     * @param focusedSelectedGlow the [Glow] used when the [NavigationDrawerItem] is enabled,
      * focused and selected
-     * @param pressedSelectedGlow the [Glow] used when the NavigationDrawerItem is enabled,
+     * @param pressedSelectedGlow the [Glow] used when the [NavigationDrawerItem] is enabled,
      * pressed and selected
      */
     fun glow(
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItemStyles.kt b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItemStyles.kt
index 027ac27..f08e865 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItemStyles.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItemStyles.kt
@@ -23,21 +23,21 @@
 import androidx.compose.ui.graphics.Shape
 
 /**
- * Defines [Shape] for all TV [Indication] states of a NavigationDrawerItem
+ * Defines [Shape] for all TV [Indication] states of a [NavigationDrawerItem]
  *
- * @constructor create an instance with arbitrary shape. See NavigationDrawerItemDefaults.shape
- * for the default shape used in a NavigationDrawerItem
+ * @constructor create an instance with arbitrary shape. See [NavigationDrawerItemDefaults.shape]
+ * for the default shape used in a [NavigationDrawerItem]
  *
- * @param shape the default shape used when the NavigationDrawerItem is enabled
- * @param focusedShape the shape used when the NavigationDrawerItem is enabled and focused
- * @param pressedShape the shape used when the NavigationDrawerItem is enabled and pressed
- * @param selectedShape the shape used when the NavigationDrawerItem is enabled and selected
- * @param disabledShape the shape used when the NavigationDrawerItem is not enabled
- * @param focusedSelectedShape the shape used when the NavigationDrawerItem is enabled,
+ * @param shape the default shape used when the [NavigationDrawerItem] is enabled
+ * @param focusedShape the shape used when the [NavigationDrawerItem] is enabled and focused
+ * @param pressedShape the shape used when the [NavigationDrawerItem] is enabled and pressed
+ * @param selectedShape the shape used when the [NavigationDrawerItem] is enabled and selected
+ * @param disabledShape the shape used when the [NavigationDrawerItem] is not enabled
+ * @param focusedSelectedShape the shape used when the [NavigationDrawerItem] is enabled,
  * focused and selected
- * @param focusedDisabledShape the shape used when the NavigationDrawerItem is not enabled
+ * @param focusedDisabledShape the shape used when the [NavigationDrawerItem] is not enabled
  * and focused
- * @param pressedSelectedShape the shape used when the NavigationDrawerItem is enabled,
+ * @param pressedSelectedShape the shape used when the [NavigationDrawerItem] is enabled,
  * pressed and selected
  */
 @ExperimentalTvMaterial3Api // TODO (b/263353219): Remove this before launching beta
@@ -95,41 +95,41 @@
 
 /**
  * Defines container & content color [Color] for all TV [Indication] states of a
- * NavigationDrawerItem
+ * [NavigationDrawerItem]
  *
- * @constructor create an instance with arbitrary colors. See NavigationDrawerItemDefaults.colors
- * for the default colors used in a NavigationDrawerItem
+ * @constructor create an instance with arbitrary colors. See [NavigationDrawerItemDefaults.colors]
+ * for the default colors used in a [NavigationDrawerItem]
  *
- * @param containerColor the default container color used when the NavigationDrawerItem is
+ * @param containerColor the default container color used when the [NavigationDrawerItem] is
  * enabled
- * @param contentColor the default content color used when the NavigationDrawerItem is enabled
+ * @param contentColor the default content color used when the [NavigationDrawerItem] is enabled
  * @param inactiveContentColor the content color used when none of the navigation items have
  * focus
- * @param focusedContainerColor the container color used when the NavigationDrawerItem is
+ * @param focusedContainerColor the container color used when the [NavigationDrawerItem] is
  * enabled and focused
- * @param focusedContentColor the content color used when the NavigationDrawerItem is enabled
+ * @param focusedContentColor the content color used when the [NavigationDrawerItem] is enabled
  * and focused
- * @param pressedContainerColor the container color used when the NavigationDrawerItem is
+ * @param pressedContainerColor the container color used when the [NavigationDrawerItem] is
  * enabled and pressed
- * @param pressedContentColor the content color used when the NavigationDrawerItem is enabled
+ * @param pressedContentColor the content color used when the [NavigationDrawerItem] is enabled
  * and pressed
- * @param selectedContainerColor the container color used when the NavigationDrawerItem is
+ * @param selectedContainerColor the container color used when the [NavigationDrawerItem] is
  * enabled and selected
- * @param selectedContentColor the content color used when the NavigationDrawerItem is
+ * @param selectedContentColor the content color used when the [NavigationDrawerItem] is
  * enabled and selected
- * @param disabledContainerColor the container color used when the NavigationDrawerItem is
+ * @param disabledContainerColor the container color used when the [NavigationDrawerItem] is
  * not enabled
- * @param disabledContentColor the content color used when the NavigationDrawerItem is not
+ * @param disabledContentColor the content color used when the [NavigationDrawerItem] is not
  * enabled
  * @param disabledInactiveContentColor the content color used when none of the navigation items
  * have focus and this item is disabled
  * @param focusedSelectedContainerColor the container color used when the
- * NavigationDrawerItem is enabled, focused and selected
- * @param focusedSelectedContentColor the content color used when the NavigationDrawerItem
+ * [NavigationDrawerItem] is enabled, focused and selected
+ * @param focusedSelectedContentColor the content color used when the [NavigationDrawerItem]
  * is enabled, focused and selected
  * @param pressedSelectedContainerColor the container color used when the
- * NavigationDrawerItem is enabled, pressed and selected
- * @param pressedSelectedContentColor the content color used when the NavigationDrawerItem is
+ * [NavigationDrawerItem] is enabled, pressed and selected
+ * @param pressedSelectedContentColor the content color used when the [NavigationDrawerItem] is
  * enabled, pressed and selected
  */
 @ExperimentalTvMaterial3Api // TODO (b/263353219): Remove this before launching beta
@@ -215,21 +215,21 @@
 }
 
 /**
- * Defines the scale for all TV [Indication] states of a NavigationDrawerItem
+ * Defines the scale for all TV [Indication] states of a [NavigationDrawerItem]
  *
- * @constructor create an instance with arbitrary scale. See NavigationDrawerItemDefaults.scale
- * for the default scale used in a NavigationDrawerItem
+ * @constructor create an instance with arbitrary scale. See [NavigationDrawerItemDefaults.scale]
+ * for the default scale used in a [NavigationDrawerItem]
  *
- * @param scale the scale used when the NavigationDrawerItem is enabled
- * @param focusedScale the scale used when the NavigationDrawerItem is enabled and focused
- * @param pressedScale the scale used when the NavigationDrawerItem is enabled and pressed
- * @param selectedScale the scale used when the NavigationDrawerItem is enabled and selected
- * @param disabledScale the scale used when the NavigationDrawerItem is not enabled
- * @param focusedSelectedScale the scale used when the NavigationDrawerItem is enabled,
+ * @param scale the scale used when the [NavigationDrawerItem] is enabled
+ * @param focusedScale the scale used when the [NavigationDrawerItem] is enabled and focused
+ * @param pressedScale the scale used when the [NavigationDrawerItem] is enabled and pressed
+ * @param selectedScale the scale used when the [NavigationDrawerItem] is enabled and selected
+ * @param disabledScale the scale used when the [NavigationDrawerItem] is not enabled
+ * @param focusedSelectedScale the scale used when the [NavigationDrawerItem] is enabled,
  * focused and selected
- * @param focusedDisabledScale the scale used when the NavigationDrawerItem is not enabled and
+ * @param focusedDisabledScale the scale used when the [NavigationDrawerItem] is not enabled and
  * focused
- * @param pressedSelectedScale the scale used when the NavigationDrawerItem is enabled,
+ * @param pressedSelectedScale the scale used when the [NavigationDrawerItem] is enabled,
  * pressed and selected
  */
 @ExperimentalTvMaterial3Api // TODO (b/263353219): Remove this before launching beta
@@ -286,7 +286,7 @@
 
     companion object {
         /**
-         * Signifies the absence of a [ScaleIndication] in NavigationDrawerItem
+         * Signifies the absence of a [ScaleIndication] in [NavigationDrawerItem]
          */
         val None = NavigationDrawerItemScale(
             scale = 1f,
@@ -302,22 +302,22 @@
 }
 
 /**
- * Defines [Border] for all TV [Indication] states of a NavigationDrawerItem
+ * Defines [Border] for all TV [Indication] states of a [NavigationDrawerItem]
  *
- * @constructor create an instance with arbitrary border. See NavigationDrawerItemDefaults.border
- * for the default border used in a NavigationDrawerItem
+ * @constructor create an instance with arbitrary border. See [NavigationDrawerItemDefaults.border]
+ * for the default border used in a [NavigationDrawerItem]
  *
- * @param border the default [Border] used when the NavigationDrawerItem is enabled
- * @param focusedBorder the [Border] used when the NavigationDrawerItem is enabled and focused
- * @param pressedBorder the [Border] used when the NavigationDrawerItem is enabled and pressed
- * @param selectedBorder the [Border] used when the NavigationDrawerItem is enabled and
+ * @param border the default [Border] used when the [NavigationDrawerItem] is enabled
+ * @param focusedBorder the [Border] used when the [NavigationDrawerItem] is enabled and focused
+ * @param pressedBorder the [Border] used when the [NavigationDrawerItem] is enabled and pressed
+ * @param selectedBorder the [Border] used when the [NavigationDrawerItem] is enabled and
  * selected
- * @param disabledBorder the [Border] used when the NavigationDrawerItem is not enabled
- * @param focusedSelectedBorder the [Border] used when the NavigationDrawerItem is enabled,
+ * @param disabledBorder the [Border] used when the [NavigationDrawerItem] is not enabled
+ * @param focusedSelectedBorder the [Border] used when the [NavigationDrawerItem] is enabled,
  * focused and selected
- * @param focusedDisabledBorder the [Border] used when the NavigationDrawerItem is not
+ * @param focusedDisabledBorder the [Border] used when the [NavigationDrawerItem] is not
  * enabled and focused
- * @param pressedSelectedBorder the [Border] used when the NavigationDrawerItem is enabled,
+ * @param pressedSelectedBorder the [Border] used when the [NavigationDrawerItem] is enabled,
  * pressed and selected
  */
 @ExperimentalTvMaterial3Api // TODO (b/263353219): Remove this before launching beta
@@ -374,18 +374,18 @@
 }
 
 /**
- * Defines [Glow] for all TV [Indication] states of a NavigationDrawerItem
+ * Defines [Glow] for all TV [Indication] states of a [NavigationDrawerItem]
  *
- * @constructor create an instance with arbitrary glow. See NavigationDrawerItemDefaults.glow
- * for the default glow used in a NavigationDrawerItem
+ * @constructor create an instance with arbitrary glow. See [NavigationDrawerItemDefaults.glow]
+ * for the default glow used in a [NavigationDrawerItem]
  *
- * @param glow the [Glow] used when the NavigationDrawerItem is enabled
- * @param focusedGlow the [Glow] used when the NavigationDrawerItem is enabled and focused
- * @param pressedGlow the [Glow] used when the NavigationDrawerItem is enabled and pressed
- * @param selectedGlow the [Glow] used when the NavigationDrawerItem is enabled and selected
- * @param focusedSelectedGlow the [Glow] used when the NavigationDrawerItem is enabled,
+ * @param glow the [Glow] used when the [NavigationDrawerItem] is enabled
+ * @param focusedGlow the [Glow] used when the [NavigationDrawerItem] is enabled and focused
+ * @param pressedGlow the [Glow] used when the [NavigationDrawerItem] is enabled and pressed
+ * @param selectedGlow the [Glow] used when the [NavigationDrawerItem] is enabled and selected
+ * @param focusedSelectedGlow the [Glow] used when the [NavigationDrawerItem] is enabled,
  * focused and selected
- * @param pressedSelectedGlow the [Glow] used when the NavigationDrawerItem is enabled,
+ * @param pressedSelectedGlow the [Glow] used when the [NavigationDrawerItem] is enabled,
  * pressed and selected
  */
 @ExperimentalTvMaterial3Api // TODO (b/263353219): Remove this before launching beta
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerScope.kt b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerScope.kt
index ccdcec7..85434a3 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerScope.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerScope.kt
@@ -17,7 +17,7 @@
 package androidx.tv.material3
 
 /**
- * [NavigationDrawerScope] is used to provide the isActivated state to the NavigationDrawerItem
+ * [NavigationDrawerScope] is used to provide the isActivated state to the [NavigationDrawerItem]
  * composable
  */
 @ExperimentalTvMaterial3Api // TODO (b/263353219): Remove this before launching beta
@@ -25,10 +25,10 @@
     /**
      * Whether any item within the [NavigationDrawer] or [ModalNavigationDrawer] is focused
      */
-    val isActivated: Boolean
+    val doesNavigationDrawerHaveFocus: Boolean
 }
 
 @OptIn(ExperimentalTvMaterial3Api::class)
-internal class NavigationDrawerScopeImpl constructor(
-    override val isActivated: Boolean
+internal class NavigationDrawerScopeImpl(
+    override val doesNavigationDrawerHaveFocus: Boolean
 ) : NavigationDrawerScope
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/BasicCurvedTextTest.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/BasicCurvedTextTest.kt
index 6ea9e3c..8611081 100644
--- a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/BasicCurvedTextTest.kt
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/BasicCurvedTextTest.kt
@@ -49,7 +49,7 @@
 
         rule.runOnIdle {
             // TODO(b/219885899): Investigate why we need the extra passes.
-            assertEquals(CapturedInfo(2, 3, 2), capturedInfo)
+            assertEquals(CapturedInfo(2, 3, 1), capturedInfo)
         }
     }
 }
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeToRevealTest.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeToRevealTest.kt
index 3140de6..9b8dbb5 100644
--- a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeToRevealTest.kt
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeToRevealTest.kt
@@ -21,9 +21,13 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.TouchInjectionScope
 import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
@@ -301,6 +305,64 @@
         undoActionModifier = Modifier.testTag(TEST_TAG)
     )
 
+    @Test
+    fun onRightSwipe_dispatchEventsToParent() {
+        var onPreScrollDispatch = 0f
+        rule.setContent {
+            val nestedScrollConnection = remember {
+                object : NestedScrollConnection {
+                    override fun onPreScroll(
+                        available: Offset,
+                        source: NestedScrollSource
+                    ): Offset {
+                        onPreScrollDispatch = available.x
+                        return available
+                    }
+                }
+            }
+            Box(
+                modifier = Modifier.nestedScroll(nestedScrollConnection)
+            ) {
+                swipeToRevealWithDefaults(
+                    modifier = Modifier.testTag(TEST_TAG)
+                )
+            }
+        }
+
+        rule.onNodeWithTag(TEST_TAG).performTouchInput { swipeRight() }
+
+        assert(onPreScrollDispatch > 0)
+    }
+
+    @Test
+    fun onLeftSwipe_dispatchEventsToParent() {
+        var onPreScrollDispatch = 0f
+        rule.setContent {
+            val nestedScrollConnection = remember {
+                object : NestedScrollConnection {
+                    override fun onPreScroll(
+                        available: Offset,
+                        source: NestedScrollSource
+                    ): Offset {
+                        onPreScrollDispatch = available.x
+                        return available
+                    }
+                }
+            }
+            Box(
+                modifier = Modifier.nestedScroll(nestedScrollConnection)
+            ) {
+                swipeToRevealWithDefaults(
+                    modifier = Modifier.testTag(TEST_TAG)
+                )
+            }
+        }
+
+        rule.onNodeWithTag(TEST_TAG).performTouchInput { swipeLeft() }
+
+        assert(onPreScrollDispatch < 0) // Swiping left means the dispatch will be negative
+    }
+
     private fun verifyLastClickAction(
         expectedClickType: RevealActionType,
         initialRevealValue: RevealValue,
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeableV2Test.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeableV2Test.kt
index 9f96a9d..5c8e950 100644
--- a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeableV2Test.kt
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeableV2Test.kt
@@ -17,13 +17,22 @@
 package androidx.wear.compose.foundation
 
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.rememberScrollableState
+import androidx.compose.foundation.gestures.scrollable
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.platform.ViewConfiguration
@@ -32,15 +41,23 @@
 import androidx.compose.ui.semantics.SemanticsProperties.VerticalScrollAxisRange
 import androidx.compose.ui.semantics.getOrNull
 import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.TouchInjectionScope
 import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeDown
+import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.test.swipeUp
 import androidx.compose.ui.unit.dp
 import kotlin.math.absoluteValue
 import org.junit.Rule
 import org.junit.Test
 
+internal const val CHILD_TEST_TAG = "childTestTag"
+
 // TODO(b/201009199) Some of these tests may need specific values adjusted when swipeableV2
 // supports property nested scrolling, but the tests should all still be valid.
 @OptIn(ExperimentalWearFoundationApi::class)
@@ -221,6 +238,260 @@
             .assert(SemanticsMatcher.keyNotDefined(VerticalScrollAxisRange))
     }
 
+    @Test
+    fun onSwipeLeft_sendsPreScrollEventToParent() {
+        var delta = 0f
+        rule.testSwipe(
+            touchInput = { swipeLeft() },
+            consumePreScrollDelta = { offset ->
+                delta = offset.x
+            }
+        )
+
+        assert(delta < 0) {
+            "Expected delta to be negative, was $delta"
+        }
+    }
+
+    @Test
+    fun onSwipeRight_sendsPreScrollEventToParent() {
+        var delta = 0f
+        rule.testSwipe(
+            touchInput = { swipeRight() },
+            consumePreScrollDelta = { offset ->
+                delta = offset.x
+            }
+        )
+
+        assert(delta > 0) {
+            "Expected delta to be positive, was $delta"
+        }
+    }
+
+    @Test
+    fun onSwipeUp_sendsPreScrollEventToParent() {
+        var delta = 0f
+        rule.testSwipe(
+            touchInput = { swipeUp() },
+            consumePreScrollDelta = { offset ->
+                delta = offset.y
+            },
+            orientation = Orientation.Vertical
+        )
+
+        assert(delta < 0) {
+            "Expected delta to be negative, was $delta"
+        }
+    }
+
+    @Test
+    fun onSwipeDown_sendsPreScrollEventToParent() {
+        var delta = 0f
+        rule.testSwipe(
+            touchInput = { swipeDown() },
+            consumePreScrollDelta = { offset ->
+                delta = offset.y
+            },
+            orientation = Orientation.Vertical
+        )
+
+        assert(delta > 0) {
+            "Expected delta to be positive, was $delta"
+        }
+    }
+
+    @Test
+    fun onSwipeLeft_sendsPostScrollEventToParent() {
+        var delta = 0f
+        rule.testSwipe(
+            touchInput = { swipeLeft() },
+            consumePostScrollDelta = { offset ->
+                delta = offset.x
+            }
+        )
+
+        assert(delta < 0) {
+            "Expected delta to be negative, was $delta"
+        }
+    }
+
+    @Test
+    fun onSwipeRight_sendsPostScrollEventToParent() {
+        var delta = 0f
+        rule.testSwipe(
+            touchInput = { swipeRight() },
+            consumePostScrollDelta = { offset ->
+                delta = offset.x
+            },
+            reverseAnchors = true //  reverse anchors or else swipeable consumes whole delta
+        )
+
+        assert(delta > 0) {
+            "Expected delta to be positive, was $delta"
+        }
+    }
+
+    @Test
+    fun onSwipeUp_sendsPostScrollEventToParent() {
+        var delta = 0f
+        rule.testSwipe(
+            touchInput = { swipeUp() },
+            consumePostScrollDelta = { offset ->
+                delta = offset.y
+            },
+            orientation = Orientation.Vertical
+        )
+
+        assert(delta < 0) {
+            "Expected delta to be negative, was $delta"
+        }
+    }
+
+    @Test
+    fun onSwipeDown_sendsPostScrollEventToParent() {
+        var delta = 0f
+        rule.testSwipe(
+            touchInput = { swipeDown() },
+            consumePostScrollDelta = { offset ->
+                delta = offset.y
+            },
+            orientation = Orientation.Vertical,
+            reverseAnchors = true //  reverse anchors or else swipeable consumes whole delta
+        )
+
+        assert(delta > 0) {
+            "Expected delta to be positive, was $delta"
+        }
+    }
+
+    @Test
+    fun onSwipeLeftToChild_sendsPreScrollEventToParent() {
+        var delta = 0f
+        rule.testSwipe(
+            touchInput = { swipeLeft() },
+            consumePreScrollDelta = { offset ->
+                delta = offset.x
+            },
+            testTag = CHILD_TEST_TAG
+        )
+
+        assert(delta < 0) {
+            "Expected delta to be negative, was $delta"
+        }
+    }
+
+    @Test
+    fun onSwipeRightToChild_sendsPreScrollEventToParent() {
+        var delta = 0f
+        rule.testSwipe(
+            touchInput = { swipeRight() },
+            consumePreScrollDelta = { offset ->
+                delta = offset.x
+            },
+            testTag = CHILD_TEST_TAG
+        )
+
+        assert(delta > 0) {
+            "Expected delta to be positive, was $delta"
+        }
+    }
+
+    private fun ComposeContentTestRule.testSwipe(
+        touchInput: TouchInjectionScope.() -> Unit,
+        consumePreScrollDelta: (Offset) -> Unit = {},
+        consumePostScrollDelta: (Offset) -> Unit = {},
+        orientation: Orientation = Orientation.Horizontal,
+        reverseAnchors: Boolean = false,
+        testTag: String = TEST_TAG
+    ) {
+        setContent {
+            val nestedScrollConnection = remember {
+                object : NestedScrollConnection {
+                    override fun onPreScroll(
+                        available: Offset,
+                        source: NestedScrollSource
+                    ): Offset {
+                        consumePreScrollDelta(available)
+                        return super.onPreScroll(available, source)
+                    }
+
+                    override fun onPostScroll(
+                        consumed: Offset,
+                        available: Offset,
+                        source: NestedScrollSource
+                    ): Offset {
+                        consumePostScrollDelta(available)
+                        return super.onPostScroll(consumed, available, source)
+                    }
+                }
+            }
+            Box(modifier = Modifier.nestedScroll(nestedScrollConnection)) {
+                SwipeableContent(
+                    orientation = orientation,
+                    reverseAnchors = reverseAnchors,
+                    modifier = Modifier.testTag(TEST_TAG)
+                ) {
+                    Box(modifier = Modifier
+                        .fillMaxSize()
+                        .testTag(CHILD_TEST_TAG)
+                        .nestedScroll(remember { object : NestedScrollConnection {} })
+                        .scrollable(
+                            state = rememberScrollableState { _ ->
+                                0f // Do not consume any delta, just return it
+                            },
+                            orientation = orientation
+                        )
+                    )
+                }
+            }
+        }
+
+        onNodeWithTag(testTag).performTouchInput { touchInput() }
+    }
+
+    @Composable
+    private fun SwipeableContent(
+        modifier: Modifier = Modifier,
+        orientation: Orientation = Orientation.Horizontal,
+        reverseAnchors: Boolean = false,
+        content: @Composable BoxScope.() -> Unit = {}
+    ) {
+        // To participate as a producer of scroll events
+        val nestedScrollDispatcher = remember { NestedScrollDispatcher() }
+        // To participate as a consumer of scroll events
+        val nestedScrollConnection = remember { object : NestedScrollConnection {} }
+        val swipeableV2State = remember {
+            SwipeableV2State(
+                initialValue = false,
+                nestedScrollDispatcher = nestedScrollDispatcher
+            )
+        }
+        val factor = if (reverseAnchors) -1 else 1
+        Box(
+            modifier = modifier
+                .fillMaxSize()
+                .nestedScroll(nestedScrollConnection)
+                .swipeableV2(
+                    swipeableV2State,
+                    orientation
+                )
+                .swipeAnchors(
+                    state = swipeableV2State,
+                    possibleValues = setOf(false, true)
+                ) { value, layoutSize ->
+                    when (value) {
+                        false -> 0f
+                        true -> factor * (
+                            if (orientation == Orientation.Horizontal) layoutSize.width.toFloat()
+                            else layoutSize.height.toFloat()
+                            )
+                    }
+                }
+                .nestedScroll(nestedScrollConnection, nestedScrollDispatcher),
+            content = content
+        )
+    }
+
     /**
      * A square [Box] has the [TEST_TAG] test tag. Touch slop is disabled to make swipe calculations
      * more exact.
diff --git a/wear/compose/compose-foundation/src/main/baseline-prof.txt b/wear/compose/compose-foundation/src/main/baseline-prof.txt
index 00329b5..edfde2d 100644
--- a/wear/compose/compose-foundation/src/main/baseline-prof.txt
+++ b/wear/compose/compose-foundation/src/main/baseline-prof.txt
@@ -30,21 +30,28 @@
 SPLandroidx/wear/compose/foundation/ExpandableItemsDefaults;->**(**)**
 HSPLandroidx/wear/compose/foundation/ExpandableKt**->**(**)**
 HSPLandroidx/wear/compose/foundation/ExpandableState;->**(**)**
-PLandroidx/wear/compose/foundation/FocusNode;->**(**)**
-HPLandroidx/wear/compose/foundation/HierarchicalFocusCoordinatorKt**->**(**)**
-PLandroidx/wear/compose/foundation/InternalMutatorMutex;->**(**)**
+SPLandroidx/wear/compose/foundation/FocusNode;->**(**)**
+HSPLandroidx/wear/compose/foundation/HierarchicalFocusCoordinatorKt**->**(**)**
+SPLandroidx/wear/compose/foundation/InternalMutatorMutex;->**(**)**
+HSPLandroidx/wear/compose/foundation/Modifiers;->**(**)**
 HSPLandroidx/wear/compose/foundation/PaddingWrapper;->**(**)**
 HSPLandroidx/wear/compose/foundation/PartialLayoutInfo;->**(**)**
 Landroidx/wear/compose/foundation/ReduceMotion;
+HSPLandroidx/wear/compose/foundation/ResourcesKt**->**(**)**
+PLandroidx/wear/compose/foundation/RevealActionType;->**(**)**
 HPLandroidx/wear/compose/foundation/RevealScopeImpl;->**(**)**
 HPLandroidx/wear/compose/foundation/RevealState;->**(**)**
 HPLandroidx/wear/compose/foundation/RevealValue;->**(**)**
-HPLandroidx/wear/compose/foundation/SwipeAnchorsModifier;->**(**)**
+HSPLandroidx/wear/compose/foundation/SwipeAnchorsModifier;->**(**)**
+HSPLandroidx/wear/compose/foundation/SwipeToDismissBoxKt**->**(**)**
+HSPLandroidx/wear/compose/foundation/SwipeToDismissBoxState;->**(**)**
+SPLandroidx/wear/compose/foundation/SwipeToDismissKeys;->**(**)**
+SPLandroidx/wear/compose/foundation/SwipeToDismissValue;->**(**)**
 PLandroidx/wear/compose/foundation/SwipeToRevealDefaults;->**(**)**
 HPLandroidx/wear/compose/foundation/SwipeToRevealKt**->**(**)**
-PLandroidx/wear/compose/foundation/SwipeableV2Defaults;->**(**)**
-HPLandroidx/wear/compose/foundation/SwipeableV2Kt**->**(**)**
-HPLandroidx/wear/compose/foundation/SwipeableV2State;->**(**)**
+SPLandroidx/wear/compose/foundation/SwipeableV2Defaults;->**(**)**
+HSPLandroidx/wear/compose/foundation/SwipeableV2Kt**->**(**)**
+HSPLandroidx/wear/compose/foundation/SwipeableV2State;->**(**)**
 SPLandroidx/wear/compose/foundation/lazy/AutoCenteringParams;->**(**)**
 HSPLandroidx/wear/compose/foundation/lazy/CombinedPaddingValues;->**(**)**
 HSPLandroidx/wear/compose/foundation/lazy/DefaultScalingLazyListItemInfo;->**(**)**
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeToReveal.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeToReveal.kt
index b2a3c52..50f3c3b 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeToReveal.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeToReveal.kt
@@ -51,6 +51,9 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
+import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
@@ -183,6 +186,7 @@
     positionalThreshold: Density.(totalDistance: Float) -> Float,
     internal val anchors: Map<RevealValue, Float>,
     internal val coroutineScope: CoroutineScope,
+    internal val nestedScrollDispatcher: NestedScrollDispatcher
 ) {
     /**
      * [SwipeableV2State] internal instance for the state.
@@ -197,6 +201,7 @@
             )
         },
         positionalThreshold = positionalThreshold,
+        nestedScrollDispatcher = nestedScrollDispatcher
     )
 
     public var lastActionType by mutableStateOf(RevealActionType.None)
@@ -340,9 +345,10 @@
     confirmValueChange: (RevealValue) -> Boolean = { true },
     positionalThreshold: Density.(totalDistance: Float) -> Float =
         SwipeToRevealDefaults.defaultThreshold(),
-    anchors: Map<RevealValue, Float> = createAnchors()
+    anchors: Map<RevealValue, Float> = createAnchors(),
 ): RevealState {
     val coroutineScope = rememberCoroutineScope()
+    val nestedScrollDispatcher = remember { NestedScrollDispatcher() }
     return remember(initialValue, animationSpec) {
         RevealState(
             initialValue = initialValue,
@@ -350,7 +356,8 @@
             confirmValueChange = confirmValueChange,
             positionalThreshold = positionalThreshold,
             anchors = anchors,
-            coroutineScope = coroutineScope
+            coroutineScope = coroutineScope,
+            nestedScrollDispatcher = nestedScrollDispatcher
         )
     }
 }
@@ -404,6 +411,8 @@
     content: @Composable () -> Unit
 ) {
     val revealScope = remember(state) { RevealScopeImpl(state) }
+    // A no-op NestedScrollConnection which does not consume scroll/fling events
+    val noOpNestedScrollConnection = remember { object : NestedScrollConnection {} }
     Box(
         modifier = modifier
             .swipeableV2(
@@ -421,6 +430,10 @@
                 // Multiply the anchor with -1f to get the actual swipeable anchor
                 -state.swipeAnchors[value]!! * swipeableWidth
             }
+            // NestedScrollDispatcher sends the scroll/fling events from the node to its parent
+            // and onwards including the modifier chain. Apply it in the end to let nested scroll
+            // connection applied before this modifier consume the scroll/fling events.
+            .nestedScroll(noOpNestedScrollConnection, state.nestedScrollDispatcher)
     ) {
         val swipeCompleted by remember {
             derivedStateOf { state.currentValue == RevealValue.Revealed }
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt
index 784c92f..94200e9 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt
@@ -39,6 +39,9 @@
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.layout.LayoutModifier
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.MeasureResult
@@ -55,6 +58,7 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
 import kotlin.math.abs
 import kotlinx.coroutines.CancellationException
@@ -126,6 +130,9 @@
         }
     }
 
+    // Update the orientation in the swipeable state
+    state.orientation = orientation
+
     return this.then(semantics).draggable(
         state = state.swipeDraggableState,
         orientation = orientation,
@@ -218,6 +225,7 @@
     internal val positionalThreshold: Density.(totalDistance: Float) -> Float =
         SwipeableV2Defaults.PositionalThreshold,
     internal val velocityThreshold: Dp = SwipeableV2Defaults.VelocityThreshold,
+    private val nestedScrollDispatcher: NestedScrollDispatcher? = null
 ) {
 
     private val swipeMutex = InternalMutatorMutex()
@@ -242,6 +250,11 @@
     }
 
     /**
+     * The orientation in which the swipeable can be swiped.
+     */
+    internal var orientation = Orientation.Horizontal
+
+    /**
      * The current value of the [SwipeableV2State].
      */
     var currentValue: T by mutableStateOf(initialValue)
@@ -427,17 +440,29 @@
      * Find the closest anchor taking into account the velocity and settle at it with an animation.
      */
     suspend fun settle(velocity: Float) {
+        var availableVelocity = velocity
+        // Dispatch the velocity to parent nodes for consuming
+        nestedScrollDispatcher?.let {
+            val consumedVelocity = nestedScrollDispatcher.dispatchPreFling(
+                if (orientation == Orientation.Horizontal) {
+                    Velocity(x = velocity, y = 0f)
+                } else {
+                    Velocity(x = 0f, y = velocity)
+                }
+            )
+            availableVelocity -= (consumedVelocity.x + consumedVelocity.y)
+        }
         val previousValue = this.currentValue
         val targetValue = computeTarget(
             offset = requireOffset(),
             currentValue = previousValue,
-            velocity = velocity
+            velocity = availableVelocity
         )
         if (confirmValueChange(targetValue)) {
-            animateTo(targetValue, velocity)
+            animateTo(targetValue, availableVelocity)
         } else {
             // If the user vetoed the state change, rollback to the previous state.
-            animateTo(previousValue, velocity)
+            animateTo(previousValue, availableVelocity)
         }
     }
 
@@ -447,14 +472,41 @@
      * @return The delta the consumed by the [SwipeableV2State]
      */
     fun dispatchRawDelta(delta: Float): Float {
+        var remainingDelta = delta
+
+        // Dispatch the delta as a scroll event to parent node for consuming it
+        nestedScrollDispatcher?.let {
+            val consumedByParent = nestedScrollDispatcher.dispatchPreScroll(
+                available = offsetWithOrientation(remainingDelta),
+                source = NestedScrollSource.Drag
+            )
+            remainingDelta -= (consumedByParent.x + consumedByParent.y)
+        }
         val currentDragPosition = offset ?: 0f
-        val potentiallyConsumed = currentDragPosition + delta
+        val potentiallyConsumed = currentDragPosition + remainingDelta
         val clamped = potentiallyConsumed.coerceIn(minOffset, maxOffset)
         val deltaToConsume = clamped - currentDragPosition
         if (abs(deltaToConsume) >= 0) {
             offset = ((offset ?: 0f) + deltaToConsume).coerceIn(minOffset, maxOffset)
         }
-        return deltaToConsume
+
+        nestedScrollDispatcher?.let {
+            val consumedDelta = nestedScrollDispatcher.dispatchPostScroll(
+                consumed = offsetWithOrientation(deltaToConsume),
+                available = offsetWithOrientation(delta - deltaToConsume),
+                source = NestedScrollSource.Drag
+            )
+            remainingDelta -= (deltaToConsume + consumedDelta.x + consumedDelta.y)
+        }
+        return remainingDelta
+    }
+
+    private fun offsetWithOrientation(delta: Float): Offset {
+        return if (orientation == Orientation.Horizontal) {
+            Offset(x = delta, y = 0f)
+        } else {
+            Offset(x = 0f, y = delta)
+        }
     }
 
     private fun computeTarget(
diff --git a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/SwipeToRevealSample.kt b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/SwipeToRevealSample.kt
new file mode 100644
index 0000000..e988234
--- /dev/null
+++ b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/SwipeToRevealSample.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
+import androidx.wear.compose.foundation.rememberRevealState
+import androidx.wear.compose.material.AppCard
+import androidx.wear.compose.material.CardDefaults
+import androidx.wear.compose.material.Chip
+import androidx.wear.compose.material.ChipDefaults
+import androidx.wear.compose.material.ExperimentalWearMaterialApi
+import androidx.wear.compose.material.Icon
+import androidx.wear.compose.material.SwipeToRevealCard
+import androidx.wear.compose.material.SwipeToRevealChip
+import androidx.wear.compose.material.SwipeToRevealDefaults
+import androidx.wear.compose.material.Text
+
+@OptIn(ExperimentalWearMaterialApi::class, ExperimentalWearFoundationApi::class)
+@Composable
+@Sampled
+fun SwipeToRevealChipSample() {
+    SwipeToRevealChip(
+        revealState = rememberRevealState(),
+        modifier = Modifier.fillMaxWidth(),
+        primaryAction = SwipeToRevealDefaults.primaryAction(
+            icon = { Icon(SwipeToRevealDefaults.Delete, "Delete") },
+            label = { Text("Delete") },
+            onClick = { /* Add the click handler here */ }
+        ),
+        secondaryAction = SwipeToRevealDefaults.secondaryAction(
+            icon = { Icon(SwipeToRevealDefaults.MoreOptions, "More Options") },
+            onClick = { /* Add the click handler here */ }
+        ),
+        undoPrimaryAction = SwipeToRevealDefaults.undoAction(
+            label = { Text("Undo") },
+            onClick = { /* Add the undo handler for primary action */ }
+        ),
+        undoSecondaryAction = SwipeToRevealDefaults.undoAction(
+            label = { Text("Undo") },
+            onClick = { /* Add the undo handler for secondary action */ }
+        )
+    ) {
+        Chip(
+            onClick = { /* Add the chip click handler here */ },
+            colors = ChipDefaults.primaryChipColors(),
+            border = ChipDefaults.outlinedChipBorder()
+        ) {
+            Text("SwipeToReveal Chip")
+        }
+    }
+}
+
+@OptIn(ExperimentalWearMaterialApi::class, ExperimentalWearFoundationApi::class)
+@Composable
+@Sampled
+fun SwipeToRevealCardSample() {
+    SwipeToRevealCard(
+        revealState = rememberRevealState(),
+        modifier = Modifier.fillMaxWidth(),
+        primaryAction = SwipeToRevealDefaults.primaryAction(
+            icon = { Icon(SwipeToRevealDefaults.Delete, "Delete") },
+            label = { Text("Delete") },
+            onClick = { /* Add the click handler here */ }
+        ),
+        secondaryAction = SwipeToRevealDefaults.secondaryAction(
+            icon = { Icon(SwipeToRevealDefaults.MoreOptions, "More Options") },
+            onClick = { /* Add the click handler here */ }
+        ),
+        undoPrimaryAction = SwipeToRevealDefaults.undoAction(
+            label = { Text("Undo") },
+            onClick = { /* Add the undo handler for primary action */ }
+        ),
+        undoSecondaryAction = SwipeToRevealDefaults.undoAction(
+            label = { Text("Undo") },
+            onClick = { /* Add the undo handler for secondary action */ }
+        )
+    ) {
+        AppCard(
+            onClick = { /* Add the Card click handler */ },
+            appName = { Text("AppName") },
+            appImage = {
+                Icon(
+                    painter = painterResource(id = R.drawable.ic_airplanemode_active_24px),
+                    contentDescription = "airplane",
+                    modifier = Modifier.size(CardDefaults.AppImageSize)
+                        .wrapContentSize(align = Alignment.Center),
+                )
+            },
+            title = { Text("AppCard") },
+            time = { Text("now") }
+        ) {
+            Text("Basic card with SwipeToReveal actions")
+        }
+    }
+}
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/MaterialTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/MaterialTest.kt
index 3cd42b0..8e2e2bc 100644
--- a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/MaterialTest.kt
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/MaterialTest.kt
@@ -16,7 +16,9 @@
 package androidx.wear.compose.material
 
 import android.graphics.Bitmap
+import android.os.Build
 import android.util.Log
+import androidx.annotation.RequiresApi
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
@@ -25,6 +27,8 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.Add
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.testutils.assertAgainstGolden
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Rect
@@ -33,10 +37,12 @@
 import androidx.compose.ui.graphics.asAndroidBitmap
 import androidx.compose.ui.graphics.toPixelMap
 import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.semantics.SemanticsNode
 import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
@@ -45,11 +51,13 @@
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpRect
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.height
 import androidx.compose.ui.unit.isUnspecified
 import androidx.compose.ui.unit.toSize
 import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.screenshot.AndroidXScreenshotTestRule
 import java.io.File
 import java.io.FileOutputStream
 import java.io.IOException
@@ -209,6 +217,25 @@
     return this
 }
 
+@RequiresApi(Build.VERSION_CODES.O)
+internal fun ComposeContentTestRule.verifyScreenshot(
+    screenshotRule: AndroidXScreenshotTestRule,
+    methodName: String,
+    testTag: String = TEST_TAG,
+    layoutDirection: LayoutDirection = LayoutDirection.Ltr,
+    content: @Composable () -> Unit
+) {
+    setContentWithTheme {
+        CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
+            content()
+        }
+    }
+
+    onNodeWithTag(testTag)
+        .captureToImage()
+        .assertAgainstGolden(screenshotRule, methodName)
+}
+
 /**
  * Asserts that the layout of this node has height equal to [expectedHeight].
  *
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealScreenshotTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealScreenshotTest.kt
new file mode 100644
index 0000000..9622c31
--- /dev/null
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SwipeToRevealScreenshotTest.kt
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
+import androidx.wear.compose.foundation.RevealActionType
+import androidx.wear.compose.foundation.RevealState
+import androidx.wear.compose.foundation.RevealValue
+import androidx.wear.compose.foundation.rememberRevealState
+import kotlinx.coroutines.launch
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@RequiresApi(Build.VERSION_CODES.O)
+@OptIn(ExperimentalWearMaterialApi::class, ExperimentalWearFoundationApi::class)
+class SwipeToRevealScreenshotTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val screenshotRule = AndroidXScreenshotTestRule(SCREENSHOT_GOLDEN_PATH)
+
+    @get:Rule
+    val testName = TestName()
+
+    @Test
+    fun swipeToRevealCard_singleAction() {
+        rule.verifyScreenshot(
+            screenshotRule = screenshotRule,
+            methodName = testName.methodName
+        ) {
+            swipeToRevealCard(
+                revealState = rememberRevealState(initialValue = RevealValue.Revealing),
+                secondaryAction = null
+            )
+        }
+    }
+
+    @Test
+    fun swipeToRevealChip_singleAction() {
+        rule.verifyScreenshot(
+            screenshotRule = screenshotRule,
+            methodName = testName.methodName
+        ) {
+            swipeToRevealChip(
+                revealState = rememberRevealState(initialValue = RevealValue.Revealing),
+                secondaryAction = null
+            )
+        }
+    }
+
+    @Test
+    fun swipeToRevealCard_twoActions() {
+        rule.verifyScreenshot(
+            screenshotRule = screenshotRule,
+            methodName = testName.methodName
+        ) {
+            swipeToRevealCard(
+                revealState = rememberRevealState(initialValue = RevealValue.Revealing)
+            )
+        }
+    }
+
+    @Test
+    fun swipeToRevealChip_twoActions() {
+        rule.verifyScreenshot(
+            screenshotRule = screenshotRule,
+            methodName = testName.methodName
+        ) {
+            swipeToRevealChip(
+                revealState = rememberRevealState(initialValue = RevealValue.Revealing)
+            )
+        }
+    }
+
+    @Test
+    fun swipeToRevealChip_undoPrimaryAction() {
+        rule.verifyScreenshot(
+            screenshotRule = screenshotRule,
+            methodName = testName.methodName
+        ) {
+            swipeToRevealChip(
+                revealState = rememberRevealState(initialValue = RevealValue.Revealed)
+            )
+        }
+    }
+
+    @Test
+    fun swipeToRevealCard_undoPrimaryAction() {
+        rule.verifyScreenshot(
+            screenshotRule = screenshotRule,
+            methodName = testName.methodName
+        ) {
+            swipeToRevealCard(
+                revealState = rememberRevealState(initialValue = RevealValue.Revealed)
+            )
+        }
+    }
+
+    @Test
+    fun swipeToRevealChip_undoSecondaryAction() {
+        rule.verifyScreenshot(
+            screenshotRule = screenshotRule,
+            methodName = testName.methodName
+        ) {
+            val revealState = rememberRevealState()
+            val coroutineScope = rememberCoroutineScope()
+            coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
+            revealState.lastActionType = RevealActionType.SecondaryAction
+            swipeToRevealChip(
+                revealState = revealState
+            )
+        }
+    }
+
+    @Test
+    fun swipeToRevealCard_undoSecondaryAction() {
+        rule.verifyScreenshot(
+            screenshotRule = screenshotRule,
+            methodName = testName.methodName
+        ) {
+            val revealState = rememberRevealState()
+            val coroutineScope = rememberCoroutineScope()
+            coroutineScope.launch { revealState.animateTo(RevealValue.Revealed) }
+            revealState.lastActionType = RevealActionType.SecondaryAction
+            swipeToRevealCard(
+                revealState = revealState
+            )
+        }
+    }
+
+    @Composable
+    private fun swipeToRevealCard(
+        revealState: RevealState = rememberRevealState(),
+        secondaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.secondaryAction(
+            icon = { Icon(SwipeToRevealDefaults.MoreOptions, "More Options") }
+        ),
+        undoPrimaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.undoAction(
+            label = { Text("Undo") }
+        ),
+        undoSecondaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.undoAction(
+            label = { Text("Undo") }
+        )
+    ) {
+        Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
+            SwipeToRevealCard(
+                modifier = Modifier.testTag(TEST_TAG),
+                primaryAction = SwipeToRevealDefaults.primaryAction(
+                    icon = { Icon(SwipeToRevealDefaults.Delete, "Delete") },
+                    label = { Text("Delete") }
+                ),
+                secondaryAction = secondaryAction,
+                undoPrimaryAction = undoPrimaryAction,
+                undoSecondaryAction = undoSecondaryAction,
+                revealState = revealState
+            ) {
+                TitleCard(
+                    onClick = { /*TODO*/ },
+                    title = { Text("Title of card") },
+                    time = { Text("now") },
+                ) {
+                    Text("Swipe To Reveal - Card")
+                }
+            }
+        }
+    }
+
+    @Composable
+    private fun swipeToRevealChip(
+        revealState: RevealState = rememberRevealState(),
+        secondaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.secondaryAction(
+            icon = { Icon(SwipeToRevealDefaults.MoreOptions, "More Options") }
+        ),
+        undoPrimaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.undoAction(
+            label = { Text("Undo") }
+        ),
+        undoSecondaryAction: SwipeToRevealAction? = SwipeToRevealDefaults.undoAction(
+            label = { Text("Undo") }
+        )
+    ) {
+        Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
+            SwipeToRevealChip(
+                modifier = Modifier.testTag(TEST_TAG),
+                primaryAction = SwipeToRevealDefaults.primaryAction(
+                    icon = { Icon(SwipeToRevealDefaults.Delete, "Delete") },
+                    label = { Text("Delete") }
+                ),
+                secondaryAction = secondaryAction,
+                undoPrimaryAction = undoPrimaryAction,
+                undoSecondaryAction = undoSecondaryAction,
+                revealState = revealState
+            ) {
+                Chip(
+                    onClick = { /* onClick handler for chip */ },
+                    colors = ChipDefaults.primaryChipColors(),
+                    border = ChipDefaults.outlinedChipBorder()
+                ) {
+                    Text("Swipe To Reveal - Chip")
+                }
+            }
+        }
+    }
+}
diff --git a/wear/compose/compose-material/src/main/baseline-prof.txt b/wear/compose/compose-material/src/main/baseline-prof.txt
index de41148..8c7b94c 100644
--- a/wear/compose/compose-material/src/main/baseline-prof.txt
+++ b/wear/compose/compose-material/src/main/baseline-prof.txt
@@ -67,7 +67,7 @@
 HPLandroidx/wear/compose/material/PlaceholderKt**->**(**)**
 PLandroidx/wear/compose/material/PlaceholderModifier;->**(**)**
 HPLandroidx/wear/compose/material/PlaceholderShimmerModifier;->**(**)**
-PLandroidx/wear/compose/material/PlaceholderStage;->**(**)**
+HPLandroidx/wear/compose/material/PlaceholderStage;->**(**)**
 HPLandroidx/wear/compose/material/PlaceholderState;->**(**)**
 HSPLandroidx/wear/compose/material/PositionIndicatorAlignment;->**(**)**
 HSPLandroidx/wear/compose/material/PositionIndicatorKt**->**(**)**
@@ -146,7 +146,12 @@
 HSPLandroidx/wear/compose/materialcore/IconKt**->**(**)**
 HPLandroidx/wear/compose/materialcore/RangeDefaults;->**(**)**
 PLandroidx/wear/compose/materialcore/RangeIcons;->**(**)**
+HPLandroidx/wear/compose/materialcore/RepeatableClickableKt**->**(**)**
+HSPLandroidx/wear/compose/materialcore/ResourcesKt**->**(**)**
+HPLandroidx/wear/compose/materialcore/SelectionControlsKt**->**(**)**
+PLandroidx/wear/compose/materialcore/SelectionStage;->**(**)**
 HPLandroidx/wear/compose/materialcore/SliderKt**->**(**)**
 PLandroidx/wear/compose/materialcore/StepperDefaults;->**(**)**
 HPLandroidx/wear/compose/materialcore/StepperKt**->**(**)**
 HSPLandroidx/wear/compose/materialcore/TextKt**->**(**)**
+HSPLandroidx/wear/compose/materialcore/ToggleButtonKt**->**(**)**
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/SwipeToReveal.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/SwipeToReveal.kt
index 1d1e5ea..56ede21 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/SwipeToReveal.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/SwipeToReveal.kt
@@ -55,6 +55,9 @@
 /**
  * [SwipeToReveal] Material composable for Chips. This provides the default style for consistency.
  *
+ * Example of [SwipeToRevealChip] with primary and secondary actions
+ * @sample androidx.wear.compose.material.samples.SwipeToRevealChipSample
+ *
  * @param primaryAction A [SwipeToRevealAction] instance to describe the primary action when
  * swiping. See [SwipeToRevealDefaults.primaryAction]. The action will be triggered on click or a
  * full swipe.
@@ -105,6 +108,9 @@
 /**
  * [SwipeToReveal] Material composable for Cards. This provides the default style for consistency.
  *
+ * Example of [SwipeToRevealCard] with primary and secondary actions
+ * @sample androidx.wear.compose.material.samples.SwipeToRevealCardSample
+ *
  * @param primaryAction A [SwipeToRevealAction] instance to describe the primary action when
  * swiping. See [SwipeToRevealDefaults.primaryAction]. The action will be triggered on click or a
  * full swipe.
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index be867cc..9b379c2 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -91,6 +91,7 @@
   }
 
   @androidx.compose.runtime.Immutable public final class CheckboxColors {
+    ctor public CheckboxColors(long checkedBoxColor, long checkedCheckmarkColor, long uncheckedBoxColor, long uncheckedCheckmarkColor, long disabledCheckedBoxColor, long disabledCheckedCheckmarkColor, long disabledUncheckedBoxColor, long disabledUncheckedCheckmarkColor);
     method public long getCheckedBoxColor();
     method public long getCheckedCheckmarkColor();
     method public long getDisabledCheckedBoxColor();
@@ -222,20 +223,20 @@
   }
 
   public final class IconButtonDefaults {
-    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors filledIconButtonColors(optional long containerColor, optional long contentColor);
-    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors filledTonalIconButtonColors(optional long containerColor, optional long contentColor);
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors filledIconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors filledTonalIconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
     method public float getDefaultButtonSize();
     method public float getDefaultIconSize();
     method public float getExtraSmallButtonSize();
     method public float getLargeButtonSize();
     method public float getLargeIconSize();
-    method public androidx.compose.foundation.shape.RoundedCornerShape getShape();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
     method public float getSmallButtonSize();
     method public float getSmallIconSize();
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors iconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
     method public float iconSizeFor(float size);
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ToggleButtonColors iconToggleButtonColors(optional long checkedContainerColor, optional long checkedContentColor, optional long uncheckedContainerColor, optional long uncheckedContentColor, optional long disabledCheckedContainerColor, optional long disabledCheckedContentColor, optional long disabledUncheckedContainerColor, optional long disabledUncheckedContentColor);
-    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors outlinedIconButtonColors(optional long contentColor);
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors outlinedIconButtonColors(optional long contentColor, optional long disabledContentColor);
     property public final float DefaultButtonSize;
     property public final float DefaultIconSize;
     property public final float ExtraSmallButtonSize;
@@ -243,7 +244,7 @@
     property public final float LargeIconSize;
     property public final float SmallButtonSize;
     property public final float SmallIconSize;
-    property public final androidx.compose.foundation.shape.RoundedCornerShape shape;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
     field public static final androidx.wear.compose.material3.IconButtonDefaults INSTANCE;
   }
 
@@ -353,6 +354,7 @@
   }
 
   @androidx.compose.runtime.Immutable public final class RadioButtonColors {
+    ctor public RadioButtonColors(long selectedColor, long unselectedColor, long disabledSelectedColor, long disabledUnselectedColor);
     method public long getDisabledSelectedColor();
     method public long getDisabledUnselectedColor();
     method public long getSelectedColor();
@@ -431,6 +433,7 @@
   }
 
   @androidx.compose.runtime.Immutable public final class SwitchColors {
+    ctor public SwitchColors(long checkedThumbColor, long checkedThumbIconColor, long checkedTrackColor, long checkedTrackBorderColor, long uncheckedThumbColor, long uncheckedThumbIconColor, long uncheckedTrackColor, long uncheckedTrackBorderColor, long disabledCheckedThumbColor, long disabledCheckedThumbIconColor, long disabledCheckedTrackColor, long disabledCheckedTrackBorderColor, long disabledUncheckedThumbColor, long disabledUncheckedThumbIconColor, long disabledUncheckedTrackColor, long disabledUncheckedTrackBorderColor);
     method public long getCheckedThumbColor();
     method public long getCheckedThumbIconColor();
     method public long getCheckedTrackBorderColor();
@@ -466,7 +469,7 @@
   }
 
   public final class SwitchDefaults {
-    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.SwitchColors colors(optional long checkedThumbColor, optional long checkedThumbIconColor, optional long checkedTrackColor, optional long checkedTrackStrokeColor, optional long uncheckedThumbColor, optional long uncheckedThumbIconColor, optional long uncheckedTrackColor, optional long uncheckedTrackStrokeColor);
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.SwitchColors colors(optional long checkedThumbColor, optional long checkedThumbIconColor, optional long checkedTrackColor, optional long checkedTrackBorderColor, optional long uncheckedThumbColor, optional long uncheckedThumbIconColor, optional long uncheckedTrackColor, optional long uncheckedTrackBorderColor);
     field public static final androidx.wear.compose.material3.SwitchDefaults INSTANCE;
   }
 
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index be867cc..9b379c2 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -91,6 +91,7 @@
   }
 
   @androidx.compose.runtime.Immutable public final class CheckboxColors {
+    ctor public CheckboxColors(long checkedBoxColor, long checkedCheckmarkColor, long uncheckedBoxColor, long uncheckedCheckmarkColor, long disabledCheckedBoxColor, long disabledCheckedCheckmarkColor, long disabledUncheckedBoxColor, long disabledUncheckedCheckmarkColor);
     method public long getCheckedBoxColor();
     method public long getCheckedCheckmarkColor();
     method public long getDisabledCheckedBoxColor();
@@ -222,20 +223,20 @@
   }
 
   public final class IconButtonDefaults {
-    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors filledIconButtonColors(optional long containerColor, optional long contentColor);
-    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors filledTonalIconButtonColors(optional long containerColor, optional long contentColor);
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors filledIconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors filledTonalIconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
     method public float getDefaultButtonSize();
     method public float getDefaultIconSize();
     method public float getExtraSmallButtonSize();
     method public float getLargeButtonSize();
     method public float getLargeIconSize();
-    method public androidx.compose.foundation.shape.RoundedCornerShape getShape();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
     method public float getSmallButtonSize();
     method public float getSmallIconSize();
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors iconButtonColors(optional long containerColor, optional long contentColor, optional long disabledContainerColor, optional long disabledContentColor);
     method public float iconSizeFor(float size);
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.ToggleButtonColors iconToggleButtonColors(optional long checkedContainerColor, optional long checkedContentColor, optional long uncheckedContainerColor, optional long uncheckedContentColor, optional long disabledCheckedContainerColor, optional long disabledCheckedContentColor, optional long disabledUncheckedContainerColor, optional long disabledUncheckedContentColor);
-    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors outlinedIconButtonColors(optional long contentColor);
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.IconButtonColors outlinedIconButtonColors(optional long contentColor, optional long disabledContentColor);
     property public final float DefaultButtonSize;
     property public final float DefaultIconSize;
     property public final float ExtraSmallButtonSize;
@@ -243,7 +244,7 @@
     property public final float LargeIconSize;
     property public final float SmallButtonSize;
     property public final float SmallIconSize;
-    property public final androidx.compose.foundation.shape.RoundedCornerShape shape;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
     field public static final androidx.wear.compose.material3.IconButtonDefaults INSTANCE;
   }
 
@@ -353,6 +354,7 @@
   }
 
   @androidx.compose.runtime.Immutable public final class RadioButtonColors {
+    ctor public RadioButtonColors(long selectedColor, long unselectedColor, long disabledSelectedColor, long disabledUnselectedColor);
     method public long getDisabledSelectedColor();
     method public long getDisabledUnselectedColor();
     method public long getSelectedColor();
@@ -431,6 +433,7 @@
   }
 
   @androidx.compose.runtime.Immutable public final class SwitchColors {
+    ctor public SwitchColors(long checkedThumbColor, long checkedThumbIconColor, long checkedTrackColor, long checkedTrackBorderColor, long uncheckedThumbColor, long uncheckedThumbIconColor, long uncheckedTrackColor, long uncheckedTrackBorderColor, long disabledCheckedThumbColor, long disabledCheckedThumbIconColor, long disabledCheckedTrackColor, long disabledCheckedTrackBorderColor, long disabledUncheckedThumbColor, long disabledUncheckedThumbIconColor, long disabledUncheckedTrackColor, long disabledUncheckedTrackBorderColor);
     method public long getCheckedThumbColor();
     method public long getCheckedThumbIconColor();
     method public long getCheckedTrackBorderColor();
@@ -466,7 +469,7 @@
   }
 
   public final class SwitchDefaults {
-    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.SwitchColors colors(optional long checkedThumbColor, optional long checkedThumbIconColor, optional long checkedTrackColor, optional long checkedTrackStrokeColor, optional long uncheckedThumbColor, optional long uncheckedThumbIconColor, optional long uncheckedTrackColor, optional long uncheckedTrackStrokeColor);
+    method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.SwitchColors colors(optional long checkedThumbColor, optional long checkedThumbIconColor, optional long checkedTrackColor, optional long checkedTrackBorderColor, optional long uncheckedThumbColor, optional long uncheckedThumbIconColor, optional long uncheckedTrackColor, optional long uncheckedTrackBorderColor);
     field public static final androidx.wear.compose.material3.SwitchDefaults INSTANCE;
   }
 
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
index 0c068d7..e8d1930 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/IconToggleButtonTest.kt
@@ -425,8 +425,8 @@
             status = Status.Disabled,
             checked = false,
             colors = { IconButtonDefaults.iconToggleButtonColors() },
-            containerColor = { MaterialTheme.colorScheme.surface },
-            contentColor = { MaterialTheme.colorScheme.onSurfaceVariant }
+            containerColor = { MaterialTheme.colorScheme.surface.toDisabledColor() },
+            contentColor = { MaterialTheme.colorScheme.onSurfaceVariant.toDisabledColor() }
         )
 
     @RequiresApi(Build.VERSION_CODES.O)
@@ -436,8 +436,8 @@
             status = Status.Disabled,
             checked = true,
             colors = { IconButtonDefaults.iconToggleButtonColors() },
-            containerColor = { MaterialTheme.colorScheme.primary },
-            contentColor = { MaterialTheme.colorScheme.onPrimary },
+            containerColor = { MaterialTheme.colorScheme.primary.toDisabledColor() },
+            contentColor = { MaterialTheme.colorScheme.onPrimary.toDisabledColor() },
         )
 
     @RequiresApi(Build.VERSION_CODES.O)
@@ -523,11 +523,11 @@
             colors = {
                 IconButtonDefaults.iconToggleButtonColors(
                     // Apply the content color override for the content alpha to be applied
-                    checkedContainerColor = overrideColor
+                    disabledCheckedContainerColor = overrideColor
                 )
             },
             containerColor = { overrideColor },
-            contentColor = { MaterialTheme.colorScheme.onPrimary }
+            contentColor = { MaterialTheme.colorScheme.onPrimary.toDisabledColor() }
         )
     }
 
@@ -542,10 +542,10 @@
             colors = {
                 IconButtonDefaults.iconToggleButtonColors(
                     // Apply the content color override for the content alpha to be applied
-                    checkedContentColor = overrideColor
+                    disabledCheckedContentColor = overrideColor
                 )
             },
-            containerColor = { MaterialTheme.colorScheme.primary },
+            containerColor = { MaterialTheme.colorScheme.primary.toDisabledColor() },
             contentColor = { overrideColor }
         )
     }
@@ -561,11 +561,11 @@
             colors = {
                 IconButtonDefaults.iconToggleButtonColors(
                     // Apply the content color override for the content alpha to be applied
-                    uncheckedContainerColor = overrideColor
+                    disabledUncheckedContainerColor = overrideColor
                 )
             },
             containerColor = { overrideColor },
-            contentColor = { MaterialTheme.colorScheme.onSurfaceVariant }
+            contentColor = { MaterialTheme.colorScheme.onSurfaceVariant.toDisabledColor() }
         )
     }
 
@@ -580,11 +580,11 @@
             colors = {
                 IconButtonDefaults.iconToggleButtonColors(
                     // Apply the content color override for the content alpha to be applied
-                    uncheckedContentColor = overrideColor
+                    disabledUncheckedContentColor = overrideColor
                 )
             },
             contentColor = { overrideColor },
-            containerColor = { MaterialTheme.colorScheme.surface }
+            containerColor = { MaterialTheme.colorScheme.surface.toDisabledColor() }
         )
     }
 
@@ -618,7 +618,9 @@
                 onCheckedChange = {},
                 enabled = false,
                 content = { TestImage() },
-                modifier = Modifier.testTag(TEST_TAG).semantics { role = overrideRole }
+                modifier = Modifier
+                    .testTag(TEST_TAG)
+                    .semantics { role = overrideRole }
             )
         }
 
@@ -639,7 +641,6 @@
         contentColor: @Composable () -> Color,
     ) {
         verifyColors(
-            status = status,
             expectedContainerColor = containerColor,
             expectedContentColor = contentColor,
             content = {
@@ -705,10 +706,8 @@
 
     @RequiresApi(Build.VERSION_CODES.O)
     private fun ComposeContentTestRule.verifyColors(
-        status: Status,
         expectedContainerColor: @Composable () -> Color,
         expectedContentColor: @Composable () -> Color,
-        applyAlphaForDisabled: Boolean = true,
         content: @Composable () -> Color
     ) {
         val testBackgroundColor = Color.White
@@ -717,17 +716,8 @@
         var actualContentColor = Color.Transparent
         setContentWithTheme {
             finalExpectedContainerColor =
-                if (status.enabled() || !applyAlphaForDisabled) {
-                    expectedContainerColor()
-                } else {
-                    expectedContainerColor().copy(ContentAlpha.disabled)
-                }.compositeOver(testBackgroundColor)
-            finalExpectedContent =
-                if (status.enabled() || !applyAlphaForDisabled) {
-                    expectedContentColor()
-                } else {
-                    expectedContentColor().copy(ContentAlpha.disabled)
-                }
+                expectedContainerColor().compositeOver(testBackgroundColor)
+            finalExpectedContent = expectedContentColor()
             Box(
                 Modifier
                     .fillMaxSize()
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsTest.kt
index f5728b5..9d77f8c 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsTest.kt
@@ -460,7 +460,7 @@
                     checkedThumbColor = thumbColor,
                     checkedThumbIconColor = thumbIconColor,
                     checkedTrackColor = trackColor,
-                    checkedTrackStrokeColor = trackStrokeColor
+                    checkedTrackBorderColor = trackStrokeColor
                 ),
                 modifier = Modifier.testTag(TEST_TAG)
             )
@@ -487,7 +487,7 @@
                     uncheckedThumbColor = thumbColor,
                     uncheckedThumbIconColor = thumbIconColor,
                     uncheckedTrackColor = trackColor,
-                    uncheckedTrackStrokeColor = trackStrokeColor
+                    uncheckedTrackBorderColor = trackStrokeColor
                 ),
                 modifier = Modifier.testTag(TEST_TAG)
             )
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt
index 7a694e8..8755ede 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt
@@ -20,7 +20,6 @@
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.BoxScope
-import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.State
@@ -30,8 +29,12 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.max
+import androidx.wear.compose.material3.tokens.FilledIconButtonTokens
+import androidx.wear.compose.material3.tokens.FilledTonalIconButtonTokens
+import androidx.wear.compose.material3.tokens.IconButtonTokens
+import androidx.wear.compose.material3.tokens.IconToggleButtonTokens
+import androidx.wear.compose.material3.tokens.OutlinedIconButtonTokens
 
 /**
  * Wear Material [IconButton] is a circular, icon-only button with transparent background and
@@ -297,7 +300,7 @@
     enabled: Boolean = true,
     colors: ToggleButtonColors = IconButtonDefaults.iconToggleButtonColors(),
     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
-    shape: Shape = CircleShape,
+    shape: Shape = IconButtonDefaults.shape,
     border: BorderStroke? = null,
     content: @Composable BoxScope.() -> Unit,
 ) {
@@ -315,7 +318,6 @@
         shape = shape,
         content = provideScopeContent(
             colors.contentColor(enabled = enabled, checked = checked),
-            MaterialTheme.typography.labelMedium,
             content
         )
     )
@@ -328,7 +330,8 @@
     /**
      * Recommended [Shape] for [IconButton].
      */
-    val shape = CircleShape
+    val shape: Shape
+        @Composable get() = IconButtonTokens.ContainerShape.value
 
     /**
      * Recommended icon size for a given icon button size.
@@ -348,20 +351,25 @@
      * If the icon button is disabled then the colors will default to
      * the MaterialTheme onSurface color with suitable alpha values applied.
      *
-     * @param containerColor The background color of this icon button when enabled
-     * @param contentColor The color of this icon button when enabled
+     * @param containerColor The background color of this icon button when enabled.
+     * @param contentColor The color of this icon when enabled.
+     * @param disabledContainerColor The background color of this icon button when not enabled.
+     * @param disabledContentColor The color of this icon when not enabled.
      */
     @Composable
     fun filledIconButtonColors(
-        containerColor: Color = MaterialTheme.colorScheme.primary,
-        contentColor: Color = MaterialTheme.colorScheme.onPrimary,
+        containerColor: Color = FilledIconButtonTokens.ContainerColor.value,
+        contentColor: Color = FilledIconButtonTokens.ContentColor.value,
+        disabledContainerColor: Color = FilledIconButtonTokens.DisabledContainerColor.value
+            .toDisabledColor(disabledAlpha = FilledIconButtonTokens.DisabledContainerOpacity),
+        disabledContentColor: Color = FilledIconButtonTokens.DisabledContentColor.value
+            .toDisabledColor(disabledAlpha = FilledIconButtonTokens.DisabledContentOpacity)
     ): IconButtonColors {
         return iconButtonColors(
             containerColor = containerColor,
             contentColor = contentColor,
-            disabledContainerColor = MaterialTheme.colorScheme.onSurface.toDisabledColor(
-                disabledAlpha = DisabledContainerAlpha
-            ),
+            disabledContainerColor = disabledContainerColor,
+            disabledContentColor = disabledContentColor
         )
     }
 
@@ -371,20 +379,25 @@
      * If the icon button is disabled then the colors will default to
      * the MaterialTheme onSurface color with suitable alpha values applied.
      *
-     * @param containerColor The background color of this icon button when enabled
-     * @param contentColor The color of this icon button when enabled
+     * @param containerColor The background color of this icon button when enabled.
+     * @param contentColor The color of this icon when enabled.
+     * @param disabledContainerColor The background color of this icon button when not enabled.
+     * @param disabledContentColor The color of this icon when not enabled.
      */
     @Composable
     fun filledTonalIconButtonColors(
-        containerColor: Color = MaterialTheme.colorScheme.surface,
-        contentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
+        containerColor: Color = FilledTonalIconButtonTokens.ContainerColor.value,
+        contentColor: Color = FilledTonalIconButtonTokens.ContentColor.value,
+        disabledContainerColor: Color = FilledTonalIconButtonTokens.DisabledContainerColor.value
+            .toDisabledColor(disabledAlpha = FilledTonalIconButtonTokens.DisabledContainerOpacity),
+        disabledContentColor: Color = FilledTonalIconButtonTokens.DisabledContentColor.value
+            .toDisabledColor(disabledAlpha = FilledTonalIconButtonTokens.DisabledContentOpacity)
     ): IconButtonColors {
         return iconButtonColors(
             containerColor = containerColor,
             contentColor = contentColor,
-            disabledContainerColor = MaterialTheme.colorScheme.onSurface.toDisabledColor(
-                disabledAlpha = DisabledContainerAlpha
-            ),
+            disabledContainerColor = disabledContainerColor,
+            disabledContentColor = disabledContentColor
         )
     }
 
@@ -394,15 +407,20 @@
      * If the icon button is disabled then the colors will default to
      * the MaterialTheme onSurface color with suitable alpha values applied.
      *
-     * @param contentColor The color of this icon button when enabled
+     * @param contentColor The color of this icon button when enabled.
+     * @param disabledContentColor The color of this icon when not enabled.
      */
     @Composable
     fun outlinedIconButtonColors(
-        contentColor: Color = MaterialTheme.colorScheme.primary,
+        contentColor: Color = OutlinedIconButtonTokens.ContentColor.value,
+        disabledContentColor: Color = OutlinedIconButtonTokens.DisabledContentColor.value
+            .toDisabledColor(OutlinedIconButtonTokens.DisabledContentOpacity)
     ): IconButtonColors {
         return iconButtonColors(
             containerColor = Color.Transparent,
             contentColor = contentColor,
+            disabledContainerColor = Color.Transparent,
+            disabledContentColor = disabledContentColor
         )
     }
 
@@ -412,17 +430,19 @@
      * If the icon button is disabled then the colors will default to
      * the MaterialTheme onSurface color with suitable alpha values applied.
      *
-     * @param containerColor the background color of this icon button when enabled
-     * @param contentColor the color of this icon when enabled
-     * @param disabledContainerColor the background color of this icon button when not enabled
-     * @param disabledContentColor the color of this icon when not enabled
+     * @param containerColor The background color of this icon button when enabled.
+     * @param contentColor The color of this icon when enabled.
+     * @param disabledContainerColor The background color of this icon button when not enabled.
+     * @param disabledContentColor The color of this icon when not enabled.
      */
     @Composable
     fun iconButtonColors(
         containerColor: Color = Color.Transparent,
-        contentColor: Color = MaterialTheme.colorScheme.onBackground,
+        contentColor: Color = IconButtonTokens.ContentColor.value,
         disabledContainerColor: Color = Color.Transparent,
-        disabledContentColor: Color = MaterialTheme.colorScheme.onSurface.toDisabledColor()
+        disabledContentColor: Color = IconButtonTokens.DisabledContentColor.value.toDisabledColor(
+            disabledAlpha = IconButtonTokens.DisabledContentOpacity
+        )
     ): IconButtonColors = IconButtonColors(
         containerColor = containerColor,
         contentColor = contentColor,
@@ -455,14 +475,19 @@
      */
     @Composable
     fun iconToggleButtonColors(
-        checkedContainerColor: Color = MaterialTheme.colorScheme.primary,
-        checkedContentColor: Color = MaterialTheme.colorScheme.onPrimary,
-        uncheckedContainerColor: Color = MaterialTheme.colorScheme.surface,
-        uncheckedContentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
-        disabledCheckedContainerColor: Color = checkedContainerColor.toDisabledColor(),
-        disabledCheckedContentColor: Color = checkedContentColor.toDisabledColor(),
-        disabledUncheckedContainerColor: Color = uncheckedContainerColor.toDisabledColor(),
-        disabledUncheckedContentColor: Color = uncheckedContentColor.toDisabledColor(),
+        checkedContainerColor: Color = IconToggleButtonTokens.CheckedContainerColor.value,
+        checkedContentColor: Color = IconToggleButtonTokens.CheckedContentColor.value,
+        uncheckedContainerColor: Color = IconToggleButtonTokens.UncheckedContainerColor.value,
+        uncheckedContentColor: Color = IconToggleButtonTokens.UncheckedContentColor.value,
+        disabledCheckedContainerColor: Color = IconToggleButtonTokens.DisabledCheckedContainerColor
+            .value.toDisabledColor(IconToggleButtonTokens.DisabledCheckedContainerOpacity),
+        disabledCheckedContentColor: Color = IconToggleButtonTokens.DisabledCheckedContentColor
+            .value.toDisabledColor(IconToggleButtonTokens.DisabledCheckedContentOpacity),
+        disabledUncheckedContainerColor: Color = IconToggleButtonTokens
+            .DisabledUncheckedContainerColor.value
+            .toDisabledColor(IconToggleButtonTokens.DisabledUncheckedContainerOpacity),
+        disabledUncheckedContentColor: Color = IconToggleButtonTokens.DisabledUncheckedContentColor
+            .value.toDisabledColor(IconToggleButtonTokens.DisabledUncheckedContentOpacity),
     ): ToggleButtonColors {
         return ToggleButtonColors(
             checkedContainerColor = checkedContainerColor,
@@ -481,43 +506,43 @@
      * [SmallButtonSize] or [ExtraSmallButtonSize].
      * Use [iconSizeFor] to easily determine the icon size.
      */
-    val SmallIconSize = 24.dp
+    val SmallIconSize = IconButtonTokens.IconSmallSize
 
     /**
      * The default size of an icon when used inside an icon button of size DefaultButtonSize.
      * Use [iconSizeFor] to easily determine the icon size.
      */
-    val DefaultIconSize = 26.dp
+    val DefaultIconSize = IconButtonTokens.IconDefaultSize
 
     /**
      * The size of an icon when used inside an icon button with size [LargeButtonSize].
      * Use [iconSizeFor] to easily determine the icon size.
      */
-    val LargeIconSize = 30.dp
+    val LargeIconSize = IconButtonTokens.IconLargeSize
 
     /**
      * The recommended background size of an extra small, compact button.
      * It is recommended to apply this size using Modifier.touchTargetAwareSize.
      */
-    val ExtraSmallButtonSize = 32.dp
+    val ExtraSmallButtonSize = IconButtonTokens.ContainerExtraSmallSize
 
     /**
      * The recommended size for a small button.
      * It is recommended to apply this size using Modifier.touchTargetAwareSize.
      */
-    val SmallButtonSize = 48.dp
+    val SmallButtonSize = IconButtonTokens.ContainerSmallSize
 
     /**
      * The default size applied for buttons.
      * It is recommended to apply this size using Modifier.touchTargetAwareSize.
      */
-    val DefaultButtonSize = 52.dp
+    val DefaultButtonSize = IconButtonTokens.ContainerDefaultSize
 
     /**
      * The recommended size for a large button.
      * It is recommended to apply this size using Modifier.touchTargetAwareSize.
      */
-    val LargeButtonSize = 60.dp
+    val LargeButtonSize = IconButtonTokens.ContainerLargeSize
 }
 
 /**
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SelectionControls.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SelectionControls.kt
index a3ef21c..0fe17a3 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SelectionControls.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SelectionControls.kt
@@ -230,13 +230,26 @@
 
 /**
  * Represents the content colors used in [Checkbox] in different states.
+ *
+ * @param checkedBoxColor The box color of [Checkbox] when enabled and checked.
+ * @param checkedCheckmarkColor The check mark color of [Checkbox] when enabled
+ * and checked.
+ * @param uncheckedBoxColor The box color of [Checkbox] when enabled and unchecked.
+ * @param uncheckedCheckmarkColor The check mark color of [Checkbox] when enabled
+ * and unchecked.
+ * @param disabledCheckedBoxColor The box color of [Checkbox] when disabled and checked.
+ * @param disabledCheckedCheckmarkColor The check mark color of [Checkbox] when disabled
+ * and checked.
+ * @param disabledUncheckedBoxColor The box color of [Checkbox] when disabled and unchecked.
+ * @param disabledUncheckedCheckmarkColor The check mark color of [Checkbox] when disabled
+ * and unchecked.
  */
 @Immutable
-class CheckboxColors internal constructor(
+class CheckboxColors(
     val checkedBoxColor: Color,
     val checkedCheckmarkColor: Color,
-    val uncheckedCheckmarkColor: Color,
     val uncheckedBoxColor: Color,
+    val uncheckedCheckmarkColor: Color,
     val disabledCheckedBoxColor: Color,
     val disabledCheckedCheckmarkColor: Color,
     val disabledUncheckedBoxColor: Color,
@@ -311,9 +324,29 @@
 
 /**
  * Represents the content colors used in [Switch] in different states.
+ *
+ * @param checkedThumbColor The thumb color of [Switch] when enabled and checked.
+ * @param checkedThumbIconColor The thumb icon color of [Switch] when enabled and checked.
+ * @param checkedTrackColor The track color of [Switch] when enabled and checked.
+ * @param checkedTrackBorderColor The track border color of [Switch] when enabled and checked.
+ * @param uncheckedThumbColor The thumb color of [Switch] when enabled and unchecked.
+ * @param uncheckedThumbIconColor The thumb icon color of [Switch] when enabled and unchecked.
+ * @param uncheckedTrackColor The track color of [Switch] when enabled and unchecked.
+ * @param uncheckedTrackBorderColor The track border color of [Switch] when enabled and unchecked.
+ * @param disabledCheckedThumbColor The thumb color of [Switch] when disabled and checked.
+ * @param disabledCheckedThumbIconColor The thumb icon color of [Switch] when disabled and checked.
+ * @param disabledCheckedTrackColor The track color of [Switch] when disabled and checked.
+ * @param disabledCheckedTrackBorderColor The track border color of [Switch] when disabled
+ * and checked.
+ * @param disabledUncheckedThumbColor The thumb color of [Switch] when disabled and unchecked.
+ * @param disabledUncheckedThumbIconColor The thumb icon color of [Switch] when disabled
+ * and unchecked.
+ * @param disabledUncheckedTrackColor The track color of [Switch] when disabled and unchecked.
+ * @param disabledUncheckedTrackBorderColor The track border color of [Switch] when disabled
+ * and unchecked.
  */
 @Immutable
-class SwitchColors internal constructor(
+class SwitchColors(
     val checkedThumbColor: Color,
     val checkedThumbIconColor: Color,
     val checkedTrackColor: Color,
@@ -426,9 +459,14 @@
 
 /**
  * Represents the content colors used in [RadioButton] in different states.
+ *
+ * @param selectedColor The color of the radio button when enabled and selected.
+ * @param unselectedColor The color of the radio button when enabled and unselected.
+ * @param disabledSelectedColor The color of the radio button when disabled and selected.
+ * @param disabledUnselectedColor The color of the radio button when disabled and unselected.
  */
 @Immutable
-class RadioButtonColors internal constructor(
+class RadioButtonColors(
     val selectedColor: Color,
     val unselectedColor: Color,
     val disabledSelectedColor: Color,
@@ -508,37 +546,37 @@
      * @param checkedThumbColor The thumb color of this [Switch] when enabled and checked.
      * @param checkedThumbIconColor The thumb icon color of this [Switch] when enabled and checked.
      * @param checkedTrackColor The track color of this [Switch] when enabled and checked.
-     * @param checkedTrackStrokeColor The track border color of this [Switch] when enabled and checked.
+     * @param checkedTrackBorderColor The track border color of this [Switch] when enabled and checked.
      * @param uncheckedThumbColor The thumb color of this [Switch] when enabled and unchecked.
      * @param uncheckedThumbIconColor The thumb icon color of this [Switch] when enabled and checked.
      * @param uncheckedTrackColor The track color of this [Switch] when enabled and unchecked.
-     * @param uncheckedTrackStrokeColor The track border color of this [Switch] when enabled and unchecked.
+     * @param uncheckedTrackBorderColor The track border color of this [Switch] when enabled and unchecked.
      */
     @Composable
     fun colors(
         checkedThumbColor: Color = MaterialTheme.colorScheme.onPrimary,
         checkedThumbIconColor: Color = MaterialTheme.colorScheme.primary,
         checkedTrackColor: Color = MaterialTheme.colorScheme.primaryDim,
-        checkedTrackStrokeColor: Color = MaterialTheme.colorScheme.primaryDim,
+        checkedTrackBorderColor: Color = MaterialTheme.colorScheme.primaryDim,
         uncheckedThumbColor: Color = MaterialTheme.colorScheme.outline,
         uncheckedThumbIconColor: Color = MaterialTheme.colorScheme.background,
         uncheckedTrackColor: Color = MaterialTheme.colorScheme.surface,
-        uncheckedTrackStrokeColor: Color = MaterialTheme.colorScheme.outline
+        uncheckedTrackBorderColor: Color = MaterialTheme.colorScheme.outline
     ): SwitchColors = SwitchColors(
         checkedThumbColor = checkedThumbColor,
         checkedThumbIconColor = checkedThumbIconColor,
         checkedTrackColor = checkedTrackColor,
-        checkedTrackBorderColor = checkedTrackStrokeColor,
+        checkedTrackBorderColor = checkedTrackBorderColor,
         uncheckedThumbColor = uncheckedThumbColor,
         uncheckedThumbIconColor = uncheckedThumbIconColor,
         uncheckedTrackColor = uncheckedTrackColor,
-        uncheckedTrackBorderColor = uncheckedTrackStrokeColor,
+        uncheckedTrackBorderColor = uncheckedTrackBorderColor,
         disabledCheckedThumbColor = checkedThumbColor.toDisabledColor(),
         disabledCheckedThumbIconColor = checkedThumbIconColor.toDisabledColor(),
         disabledCheckedTrackColor = checkedTrackColor.toDisabledColor(
             disabledAlpha = DisabledContainerAlpha
         ),
-        disabledCheckedTrackBorderColor = checkedTrackStrokeColor.toDisabledColor(
+        disabledCheckedTrackBorderColor = checkedTrackBorderColor.toDisabledColor(
             disabledAlpha = DisabledBorderAlpha
         ),
         disabledUncheckedThumbColor = uncheckedThumbColor.toDisabledColor(),
@@ -546,7 +584,7 @@
         disabledUncheckedTrackColor = uncheckedTrackColor.toDisabledColor(
             disabledAlpha = DisabledContainerAlpha
         ),
-        disabledUncheckedTrackBorderColor = uncheckedTrackStrokeColor.toDisabledColor(
+        disabledUncheckedTrackBorderColor = uncheckedTrackBorderColor.toDisabledColor(
             disabledAlpha = DisabledBorderAlpha
         )
     )
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledIconButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledIconButtonTokens.kt
new file mode 100644
index 0000000..82324a5
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledIconButtonTokens.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// VERSION: v0_12
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.wear.compose.material3.tokens
+
+internal object FilledIconButtonTokens {
+  val ContainerColor = ColorSchemeKeyTokens.Primary
+  val ContentColor = ColorSchemeKeyTokens.OnPrimary
+  val DisabledContainerColor = ColorSchemeKeyTokens.OnSurface
+  val DisabledContainerOpacity = 0.12f
+  val DisabledContentColor = ColorSchemeKeyTokens.OnSurface
+  val DisabledContentOpacity = 0.38f
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalIconButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalIconButtonTokens.kt
new file mode 100644
index 0000000..e2c42f5
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/FilledTonalIconButtonTokens.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// VERSION: v0_12
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.wear.compose.material3.tokens
+
+internal object FilledTonalIconButtonTokens {
+  val ContainerColor = ColorSchemeKeyTokens.Surface
+  val ContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
+  val DisabledContainerColor = ColorSchemeKeyTokens.OnSurface
+  val DisabledContainerOpacity = 0.12f
+  val DisabledContentColor = ColorSchemeKeyTokens.OnSurface
+  val DisabledContentOpacity = 0.38f
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconButtonTokens.kt
new file mode 100644
index 0000000..2eb7943
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconButtonTokens.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// VERSION: v0_13
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.wear.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object IconButtonTokens {
+  val ContainerDefaultSize = 52.0.dp
+  val ContainerExtraSmallSize = 32.0.dp
+  val ContainerLargeSize = 60.0.dp
+  val ContainerShape = ShapeKeyTokens.CornerFull
+  val ContainerSmallSize = 48.0.dp
+  val ContentColor = ColorSchemeKeyTokens.OnBackground
+  val DisabledContentColor = ColorSchemeKeyTokens.OnSurface
+  val DisabledContentOpacity = 0.38f
+  val IconDefaultSize = 26.0.dp
+  val IconLargeSize = 30.0.dp
+  val IconSmallSize = 24.0.dp
+}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconToggleButtonTokens.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconToggleButtonTokens.kt
new file mode 100644
index 0000000..b6ae374
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/IconToggleButtonTokens.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// VERSION: v0_16
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.wear.compose.material3.tokens
+
+internal object IconToggleButtonTokens {
+    val CheckedContainerColor = ColorSchemeKeyTokens.Primary
+    val CheckedContentColor = ColorSchemeKeyTokens.OnPrimary
+    val DisabledCheckedContainerColor = ColorSchemeKeyTokens.Primary
+    val DisabledCheckedContainerOpacity = 0.38f
+    val DisabledCheckedContentColor = ColorSchemeKeyTokens.OnPrimary
+    val DisabledCheckedContentOpacity = 0.38f
+    val DisabledUncheckedContainerColor = ColorSchemeKeyTokens.Surface
+    val DisabledUncheckedContainerOpacity = 0.38f
+    val DisabledUncheckedContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
+    val DisabledUncheckedContentOpacity = 0.38f
+    val UncheckedContainerColor = ColorSchemeKeyTokens.Surface
+    val UncheckedContentColor = ColorSchemeKeyTokens.OnSurfaceVariant
+}
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/OutlinedIconButtonTokens.kt
similarity index 61%
copy from camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
copy to wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/OutlinedIconButtonTokens.kt
index 7a355f8..e32f5b5 100644
--- a/camera/camera-effects/src/main/java/androidx/camera/effects/Portrait.java
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/OutlinedIconButtonTokens.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,13 @@
  * limitations under the License.
  */
 
-package androidx.camera.effects;
+// VERSION: v0_12
+// GENERATED CODE - DO NOT MODIFY BY HAND
 
-import androidx.annotation.RestrictTo;
+package androidx.wear.compose.material3.tokens
 
-/**
- * Provides a portrait post-processing effect.
- *
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class Portrait {
-    // TODO: implement this
+internal object OutlinedIconButtonTokens {
+  val ContentColor = ColorSchemeKeyTokens.Primary
+  val DisabledContentColor = ColorSchemeKeyTokens.OnSurface
+  val DisabledContentOpacity = 0.38f
 }
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt
index c6f8d33..a13d6d5 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt
@@ -41,6 +41,8 @@
 import androidx.wear.compose.foundation.samples.SwipeToRevealWithExpandables
 import androidx.wear.compose.integration.demos.common.ComposableDemo
 import androidx.wear.compose.integration.demos.common.DemoCategory
+import androidx.wear.compose.material.samples.SwipeToRevealCardSample
+import androidx.wear.compose.material.samples.SwipeToRevealChipSample
 
 // Declare the swipe to dismiss demos so that we can use this variable as the background composable
 // for the SwipeToDismissDemo itself.
@@ -167,6 +169,15 @@
                 },
                 ComposableDemo("Swipe To Reveal - Undo") {
                     SwipeToRevealWithDifferentUndo()
+                },
+                ComposableDemo("S2R + EdgeSwipeToDismiss") { params ->
+                    SwipeToRevealWithEdgeSwipeToDismiss(params.navigateBack)
+                },
+                ComposableDemo("Material S2R Chip") {
+                    SwipeToRevealChipSample()
+                },
+                ComposableDemo("Material S2R Card") {
+                    SwipeToRevealCardSample()
                 }
             )
         )
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt
index a8029d3..e37c1fc 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SwipeToRevealDemo.kt
@@ -16,6 +16,7 @@
 
 package androidx.wear.compose.integration.demos
 
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -43,12 +44,15 @@
 import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
 import androidx.wear.compose.foundation.RevealActionType
 import androidx.wear.compose.foundation.RevealValue
+import androidx.wear.compose.foundation.SwipeToDismissBox
 import androidx.wear.compose.foundation.createAnchors
+import androidx.wear.compose.foundation.edgeSwipeToDismiss
 import androidx.wear.compose.foundation.expandableItem
 import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
 import androidx.wear.compose.foundation.rememberExpandableState
 import androidx.wear.compose.foundation.rememberExpandableStateMapping
 import androidx.wear.compose.foundation.rememberRevealState
+import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState
 import androidx.wear.compose.material.AppCard
 import androidx.wear.compose.material.Chip
 import androidx.wear.compose.material.ChipDefaults
@@ -240,6 +244,45 @@
     }
 }
 
+@OptIn(ExperimentalWearMaterialApi::class, ExperimentalWearFoundationApi::class)
+@Composable
+fun SwipeToRevealWithEdgeSwipeToDismiss(
+    navigateBack: () -> Unit
+) {
+    val swipeToDismissBoxState = rememberSwipeToDismissBoxState()
+    SwipeToDismissBox(
+        state = swipeToDismissBoxState,
+        onDismissed = navigateBack
+    ) {
+        ScalingLazyColumn(
+            contentPadding = PaddingValues(0.dp)
+        ) {
+            repeat(5) {
+                item {
+                    SwipeToRevealChip(
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .edgeSwipeToDismiss(swipeToDismissBoxState),
+                        primaryAction = SwipeToRevealDefaults.primaryAction(
+                            icon = { Icon(SwipeToRevealDefaults.Delete, "Delete") },
+                            label = { Text("Delete") }),
+                        revealState = rememberRevealState()
+                    ) {
+                        Chip(
+                            onClick = { /*TODO*/ },
+                            colors = ChipDefaults.secondaryChipColors(),
+                            modifier = Modifier.fillMaxWidth(),
+                            label = {
+                                Text("S2R Chip with defaults")
+                            }
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
+
 @OptIn(ExperimentalWearFoundationApi::class, ExperimentalWearMaterialApi::class)
 @Composable
 private fun SwipeToRevealChipExpandable(
diff --git a/wear/compose/integration-tests/macrobenchmark-target/build.gradle b/wear/compose/integration-tests/macrobenchmark-target/build.gradle
index af518ce..5ed6e31 100644
--- a/wear/compose/integration-tests/macrobenchmark-target/build.gradle
+++ b/wear/compose/integration-tests/macrobenchmark-target/build.gradle
@@ -44,7 +44,7 @@
     implementation(project(":compose:runtime:runtime"))
     implementation(project(":compose:ui:ui-tooling"))
     implementation(project(":compose:material:material-icons-core"))
-    implementation("androidx.activity:activity-compose:1.3.1")
+    implementation(project(":activity:activity-compose"))
     implementation(project(":profileinstaller:profileinstaller"))
     implementation project(path: ':wear:compose:compose-foundation')
     implementation project(path: ':wear:compose:compose-material')
diff --git a/wear/compose/integration-tests/macrobenchmark/build.gradle b/wear/compose/integration-tests/macrobenchmark/build.gradle
index 61090b4..1b3a97c 100644
--- a/wear/compose/integration-tests/macrobenchmark/build.gradle
+++ b/wear/compose/integration-tests/macrobenchmark/build.gradle
@@ -23,7 +23,6 @@
 android {
     defaultConfig {
         minSdkVersion 29
-        testInstrumentationRunnerArguments["androidx.benchmark.fullTracing.enable"] = "true"
     }
     namespace "androidx.wear.compose.integration.macrobenchmark"
     targetProjectPath = ":wear:compose:integration-tests:macrobenchmark-target"
diff --git a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeBenchmark.kt b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeBenchmark.kt
index c9871fe..25d3741 100644
--- a/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeBenchmark.kt
+++ b/wear/compose/integration-tests/macrobenchmark/src/main/java/androidx/wear/compose/integration/macrobenchmark/SwipeBenchmark.kt
@@ -40,8 +40,6 @@
     @get:Rule
     val benchmarkRule = MacrobenchmarkRule()
 
-    private lateinit var device: UiDevice
-
     @Before
     fun setUp() {
         val instrumentation = InstrumentationRegistry.getInstrumentation()
@@ -64,8 +62,8 @@
             val swipeToDismissBox = device.findObject(By.desc(CONTENT_DESCRIPTION))
             // Setting a gesture margin is important otherwise gesture nav is triggered.
             swipeToDismissBox.setGestureMargin(device.displayWidth / 5)
-            repeat(10) {
-                swipeToDismissBox.swipe(Direction.RIGHT, 0.75f)
+            repeat(3) {
+                swipeToDismissBox.swipe(Direction.RIGHT, 0.75f, SWIPE_SPEED)
                 device.waitForIdle()
             }
         }
@@ -80,4 +78,7 @@
         @JvmStatic
         fun parameters() = createCompilationParams()
     }
+
+    private lateinit var device: UiDevice
+    private val SWIPE_SPEED = 500
 }
diff --git a/wear/compose/integration-tests/navigation/build.gradle b/wear/compose/integration-tests/navigation/build.gradle
index 5bd009f..a53ba3c 100644
--- a/wear/compose/integration-tests/navigation/build.gradle
+++ b/wear/compose/integration-tests/navigation/build.gradle
@@ -58,5 +58,4 @@
     // but it doesn't work in androidx.
     // See aosp/1804059
     androidTestImplementation "androidx.lifecycle:lifecycle-common-java8:2.4.0"
-    androidTestImplementation(project(":annotation:annotation"))
 }
\ No newline at end of file
diff --git a/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceUpdateRequester.kt b/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceUpdateRequester.kt
index 13b3f52..bf7e252 100644
--- a/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceUpdateRequester.kt
+++ b/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceUpdateRequester.kt
@@ -57,6 +57,9 @@
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         public const val UPDATE_REQUEST_RECEIVER_PACKAGE = "com.google.android.wearable.app"
 
+        /** An override to [UPDATE_REQUEST_RECEIVER_PACKAGE] for tests. */
+        internal var overrideUpdateRequestsReceiverPackage: String? = null
+
         /**
          * Creates a [ComplicationDataSourceUpdateRequester].
          *
@@ -100,9 +103,13 @@
     private val complicationDataSourceComponent: ComponentName
 ) : ComplicationDataSourceUpdateRequester {
 
+    private fun updateRequestReceiverPackage() =
+        ComplicationDataSourceUpdateRequester.overrideUpdateRequestsReceiverPackage
+            ?: ComplicationDataSourceUpdateRequester.UPDATE_REQUEST_RECEIVER_PACKAGE
+
     override fun requestUpdateAll() {
         val intent = Intent(ComplicationDataSourceUpdateRequester.ACTION_REQUEST_UPDATE_ALL)
-        intent.setPackage(ComplicationDataSourceUpdateRequester.UPDATE_REQUEST_RECEIVER_PACKAGE)
+        intent.setPackage(updateRequestReceiverPackage())
         intent.putExtra(
             ComplicationDataSourceUpdateRequester.EXTRA_PROVIDER_COMPONENT,
             complicationDataSourceComponent
@@ -117,7 +124,7 @@
 
     override fun requestUpdate(vararg complicationInstanceIds: Int) {
         val intent = Intent(ComplicationDataSourceUpdateRequester.ACTION_REQUEST_UPDATE)
-        intent.setPackage(ComplicationDataSourceUpdateRequester.UPDATE_REQUEST_RECEIVER_PACKAGE)
+        intent.setPackage(updateRequestReceiverPackage())
         intent.putExtra(
             ComplicationDataSourceUpdateRequester.EXTRA_PROVIDER_COMPONENT,
             complicationDataSourceComponent
diff --git a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableWatchFaceServiceTest.kt b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableWatchFaceServiceTest.kt
index b12573c..77b9215 100644
--- a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableWatchFaceServiceTest.kt
+++ b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableWatchFaceServiceTest.kt
@@ -102,6 +102,7 @@
     private var surfaceHolderOverride: SurfaceHolder,
 ) : ListenableWatchFaceRuntimeService() {
     lateinit var lastResourceOnlyWatchFacePackageName: String
+    val lastResourceOnlyWatchFacePackageNameLatch = CountDownLatch(1)
 
     init {
         attachBaseContext(testContext)
@@ -117,6 +118,7 @@
         resourceOnlyWatchFacePackageName: String
     ): ListenableFuture<WatchFace> {
         lastResourceOnlyWatchFacePackageName = resourceOnlyWatchFacePackageName
+        lastResourceOnlyWatchFacePackageNameLatch.countDown()
 
         val future = SettableFuture.create<WatchFace>()
         // Post a task to resolve the future.
@@ -210,9 +212,12 @@
 
         val client = awaitWithTimeout(deferredClient)
 
-        // To avoid a race condition, we need to wait for the watchface to be fully created, which
-        // this does.
-        client.complicationSlotsState
+        Assert.assertTrue(
+            service.lastResourceOnlyWatchFacePackageNameLatch.await(
+                TIME_OUT_MILLIS,
+                TimeUnit.MILLISECONDS
+            )
+        )
 
         assertThat(service.lastResourceOnlyWatchFacePackageName).isEqualTo("com.example.wf")
 
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
index c36b80e..e8f57d7 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
@@ -28,6 +28,7 @@
 import androidx.annotation.Px
 import androidx.annotation.RestrictTo
 import androidx.annotation.UiThread
+import androidx.annotation.VisibleForTesting
 import androidx.annotation.WorkerThread
 import androidx.wear.watchface.RenderParameters.HighlightedElement
 import androidx.wear.watchface.complications.ComplicationSlotBounds
@@ -243,8 +244,8 @@
     public const val ROUND_RECT: Int = 0
 
     /**
-     * For a full screen image complication slot drawn behind the watch face. Note you can only
-     * have a single background complication slot.
+     * For a full screen image complication slot drawn behind the watch face. Note you can only have
+     * a single background complication slot.
      */
     public const val BACKGROUND: Int = 1
 
@@ -342,21 +343,6 @@
  * expanded by [ComplicationSlotBounds.perComplicationTypeMargins]. Expanded bounds can overlap so
  * the [ComplicationSlot] with the lowest id that intersects the coordinates, if any, is selected.
  *
- * @param accessibilityTraversalIndex Used to sort Complications when generating accessibility
- *   content description labels.
- * @param bounds The complication slot's [ComplicationSlotBounds].
- * @param supportedTypes The list of [ComplicationType]s accepted by this complication slot, must be
- *   non-empty. During complication data source selection, each item in this list is compared in
- *   turn with entries from a data source's data source's supported types. The first matching entry
- *   from `supportedTypes` is chosen. If there are no matches then that data source is not eligible
- *   to be selected in this slot.
- * @param defaultPolicy The [DefaultComplicationDataSourcePolicy] which controls the initial
- *   complication data source when the watch face is first installed.
- * @param defaultDataSourceType The default [ComplicationType] for the default complication data
- *   source.
- * @param configExtras Extras to be merged into the Intent sent when invoking the complication data
- *   source chooser activity. This features is intended for OEM watch faces where they have elements
- *   that behave like a complication but are in fact entirely watch face specific.
  * @property id The Watch Face's ID for the complication slot.
  * @property boundsType The [ComplicationSlotBoundsTypeIntDef] of the complication slot.
  * @property canvasComplicationFactory The [CanvasComplicationFactory] used to generate a
@@ -373,6 +359,25 @@
  *   complication slot.
  */
 public class ComplicationSlot
+/**
+ * Constructs a [ComplicationSlot].
+ *
+ * @param accessibilityTraversalIndex Used to sort Complications when generating accessibility
+ *   content description labels.
+ * @param bounds The complication slot's [ComplicationSlotBounds].
+ * @param supportedTypes The list of [ComplicationType]s accepted by this complication slot, must be
+ *   non-empty. During complication data source selection, each item in this list is compared in
+ *   turn with entries from a data source's data source's supported types. The first matching entry
+ *   from `supportedTypes` is chosen. If there are no matches then that data source is not eligible
+ *   to be selected in this slot.
+ * @param defaultPolicy The [DefaultComplicationDataSourcePolicy] which controls the initial
+ *   complication data source when the watch face is first installed.
+ * @param defaultDataSourceType The default [ComplicationType] for the default complication data
+ *   source.
+ * @param configExtras Extras to be merged into the Intent sent when invoking the complication data
+ *   source chooser activity. This features is intended for OEM watch faces where they have elements
+ *   that behave like a complication but are in fact entirely watch face specific.
+ */
 @ComplicationExperimental
 internal constructor(
     public val id: Int,
@@ -391,8 +396,7 @@
     screenReaderNameResourceId: Int?,
     // TODO(b/230364881): This should really be public but some metalava bug is preventing
     // @ComplicationExperimental from working on the getter so it's currently hidden.
-    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public val boundingArc: BoundingArc?
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public val boundingArc: BoundingArc?
 ) {
     /**
      * The [ComplicationSlotsManager] this is attached to. Only set after the
@@ -430,7 +434,8 @@
 
     private var lastComplicationUpdate = Instant.EPOCH
 
-    private class ComplicationDataHistoryEntry(
+    @VisibleForTesting
+    internal class ComplicationDataHistoryEntry(
         val complicationData: ComplicationData,
         val time: Instant
     )
@@ -439,7 +444,8 @@
      * There doesn't seem to be a convenient ring buffer in the standard library so implement our
      * own one.
      */
-    private class RingBuffer(val size: Int) : Iterable<ComplicationDataHistoryEntry> {
+    @VisibleForTesting
+    internal class RingBuffer(val size: Int) : Iterable<ComplicationDataHistoryEntry> {
         private val entries = arrayOfNulls<ComplicationDataHistoryEntry>(size)
         private var readIndex = 0
         private var writeIndex = 0
@@ -467,9 +473,11 @@
 
     /**
      * In userdebug builds maintain a history of the last [MAX_COMPLICATION_HISTORY_ENTRIES]-1
-     * complications, which is logged in dumpsys to help debug complication issues.
+     * complications sent by the system, which is logged in dumpsys to help debug complication
+     * issues.
      */
-    private val complicationHistory =
+    @VisibleForTesting
+    internal val complicationHistory =
         if (Build.TYPE.equals("userdebug")) {
             RingBuffer(MAX_COMPLICATION_HISTORY_ENTRIES)
         } else {
@@ -666,8 +674,11 @@
             )
     }
 
+    /** Builder for constructing [ComplicationSlot]s. */
+    @OptIn(ComplicationExperimental::class)
+    public class Builder
     /**
-     * Builder for constructing [ComplicationSlot]s.
+     * Constructs a [Builder].
      *
      * @param id The watch face's ID for this complication. Can be any integer but should be unique
      *   within the watch face.
@@ -683,8 +694,6 @@
      * @param complicationTapFilter The [ComplicationTapFilter] used to perform hit testing for this
      *   complication.
      */
-    @OptIn(ComplicationExperimental::class)
-    public class Builder
     internal constructor(
         private val id: Int,
         private val canvasComplicationFactory: CanvasComplicationFactory,
@@ -1010,18 +1019,38 @@
 
     /**
      * Sets the current [ComplicationData] and if it's a timeline, the correct override for
-     * [instant] is chosen.
+     * [instant] is chosen. Any images associated with the complication are loaded asynchronously
+     * and the complication history is updated.
      */
-    internal fun setComplicationData(
-        complicationData: ComplicationData,
-        loadDrawablesAsynchronous: Boolean,
-        instant: Instant
-    ) {
+    internal fun setComplicationData(complicationData: ComplicationData, instant: Instant) {
         lastComplicationUpdate = instant
         complicationHistory?.push(ComplicationDataHistoryEntry(complicationData, instant))
         timelineComplicationData = complicationData
         timelineEntries = complicationData.asWireComplicationData().timelineEntries?.toList()
-        selectComplicationDataForInstant(instant, loadDrawablesAsynchronous, true)
+        selectComplicationDataForInstant(
+            instant,
+            loadDrawablesAsynchronous = true,
+            forceUpdate = true
+        )
+    }
+
+    /**
+     * Sets the current [ComplicationData] and if it's a timeline, the correct override for
+     * [instant] is chosen. Any images are loaded synchronously. The complication history is not
+     * updated.
+     */
+    internal fun setComplicationDataForScreenshot(
+        complicationData: ComplicationData,
+        instant: Instant
+    ) {
+        lastComplicationUpdate = instant
+        timelineComplicationData = complicationData
+        timelineEntries = complicationData.asWireComplicationData().timelineEntries?.toList()
+        selectComplicationDataForInstant(
+            instant,
+            loadDrawablesAsynchronous = false,
+            forceUpdate = true
+        )
     }
 
     /**
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlotsManager.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlotsManager.kt
index d9cdbd9..22d34a0 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlotsManager.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/ComplicationSlotsManager.kt
@@ -327,14 +327,14 @@
             return
         }
         complication.dataDirty = complication.dataDirty || (complication.renderer.getData() != data)
-        complication.setComplicationData(data, true, instant)
+        complication.setComplicationData(data, instant)
     }
 
     /**
      * For use by screen shot code which will reset the data afterwards, hence dirty bit not set.
      */
     @UiThread
-    internal fun setComplicationDataUpdateSync(
+    internal fun setComplicationDataUpdateForScreenshot(
         complicationSlotId: Int,
         data: ComplicationData,
         instant: Instant
@@ -348,7 +348,7 @@
             )
             return
         }
-        complication.setComplicationData(data, false, instant)
+        complication.setComplicationDataForScreenshot(data, instant)
     }
 
     /**
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index b2e238e..74af498 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -252,8 +252,9 @@
                     }
                     .apply { setContext(context) }
 
-            val engine = watchFaceService.createHeadlessEngine(componentName)
-                as WatchFaceService.EngineWrapper
+            val engine =
+                watchFaceService.createHeadlessEngine(componentName)
+                    as WatchFaceService.EngineWrapper
             val headlessWatchFaceImpl = engine.createHeadlessInstance(params)
             return engine.deferredWatchFaceImpl.await().WFEditorDelegate(headlessWatchFaceImpl)
         }
@@ -472,10 +473,7 @@
         }
     }
 
-    /**
-     * The [OverlayStyle]. This feature is unimplemented on any platform, and will be
-     * removed.
-     */
+    /** The [OverlayStyle]. This feature is unimplemented on any platform, and will be removed. */
     @Deprecated("OverlayStyle will be removed in a future release.")
     @Suppress("Deprecation")
     public var overlayStyle: OverlayStyle = OverlayStyle()
@@ -655,8 +653,7 @@
     private val tapListener = watchface.tapListener
     internal var complicationDeniedDialogIntent = watchface.complicationDeniedDialogIntent
     internal var complicationRationaleDialogIntent = watchface.complicationRationaleDialogIntent
-    @Suppress("Deprecation")
-    internal var overlayStyle = watchface.overlayStyle
+    @Suppress("Deprecation") internal var overlayStyle = watchface.overlayStyle
 
     private var mockTime = MockTime(1.0, 0, Long.MAX_VALUE)
 
@@ -671,27 +668,25 @@
 
     init {
         val context = watchFaceHostApi.getContext()
-        val displayManager =
-                context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+        val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
         displayManager.registerDisplayListener(
-                object : DisplayManager.DisplayListener {
-                    override fun onDisplayAdded(displayId: Int) {}
+            object : DisplayManager.DisplayListener {
+                override fun onDisplayAdded(displayId: Int) {}
 
-                    override fun onDisplayChanged(displayId: Int) {
-                        val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY)!!
-                        if (display.state == Display.STATE_OFF &&
-                                watchState.isVisible.value == false) {
-                            // We want to avoid a glimpse of a stale time when transitioning from
-                            // hidden to visible, so we render two black frames to clear the buffers
-                            // when the display has been turned off and the watch is not visible.
-                            renderer.renderBlackFrame()
-                            renderer.renderBlackFrame()
-                        }
+                override fun onDisplayChanged(displayId: Int) {
+                    val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY)!!
+                    if (display.state == Display.STATE_OFF && watchState.isVisible.value == false) {
+                        // We want to avoid a glimpse of a stale time when transitioning from
+                        // hidden to visible, so we render two black frames to clear the buffers
+                        // when the display has been turned off and the watch is not visible.
+                        renderer.renderBlackFrame()
+                        renderer.renderBlackFrame()
                     }
+                }
 
-                    override fun onDisplayRemoved(displayId: Int) {}
-                },
-                watchFaceHostApi.getUiThreadHandler()
+                override fun onDisplayRemoved(displayId: Int) {}
+            },
+            watchFaceHostApi.getUiThreadHandler()
         )
     }
 
@@ -893,8 +888,11 @@
             get() = watchFaceHostApi.getComplicationRationaleIntent()
 
         override var editorObscuresWatchFace: Boolean
-            get() = InteractiveInstanceManager
-                .getCurrentInteractiveInstance()?.engine?.editorObscuresWatchFace ?: false
+            get() =
+                InteractiveInstanceManager.getCurrentInteractiveInstance()
+                    ?.engine
+                    ?.editorObscuresWatchFace
+                    ?: false
             set(value) {
                 InteractiveInstanceManager.getCurrentInteractiveInstance()?.engine?.let {
                     it.editorObscuresWatchFace = value
@@ -915,7 +913,7 @@
 
                 slotIdToComplicationData?.let {
                     for ((id, complicationData) in it) {
-                        complicationSlotsManager.setComplicationDataUpdateSync(
+                        complicationSlotsManager.setComplicationDataUpdateForScreenshot(
                             id,
                             complicationData,
                             instant
@@ -930,7 +928,7 @@
                 slotIdToComplicationData?.let {
                     val now = getNow()
                     for ((id, complicationData) in oldComplicationData) {
-                        complicationSlotsManager.setComplicationDataUpdateSync(
+                        complicationSlotsManager.setComplicationDataUpdateForScreenshot(
                             id,
                             complicationData,
                             now
@@ -994,8 +992,11 @@
         // Separate calls are issued to deliver the state of isAmbient and isVisible, so during init
         // we might not yet know the state of both (which is required by the shouldAnimate logic).
         // If the editor is obscuring the watch face, there's no need to schedule a frame.
-        if (!watchState.isAmbient.hasValue() || !watchState.isVisible.hasValue() ||
-            editorObscuresWatchFace) {
+        if (
+            !watchState.isAmbient.hasValue() ||
+                !watchState.isVisible.hasValue() ||
+                editorObscuresWatchFace
+        ) {
             return
         }
 
@@ -1194,7 +1195,7 @@
 
             params.idAndComplicationDatumWireFormats?.let {
                 for (idAndData in it) {
-                    complicationSlotsManager.setComplicationDataUpdateSync(
+                    complicationSlotsManager.setComplicationDataUpdateForScreenshot(
                         idAndData.id,
                         idAndData.complicationData.toApiComplicationData(),
                         instant
@@ -1218,7 +1219,7 @@
                 if (params.idAndComplicationDatumWireFormats != null) {
                     val now = getNow()
                     for ((id, complicationData) in oldComplicationData) {
-                        complicationSlotsManager.setComplicationDataUpdateSync(
+                        complicationSlotsManager.setComplicationDataUpdateForScreenshot(
                             id,
                             complicationData,
                             now
@@ -1272,20 +1273,21 @@
 
                 // Compute the bounds of the complication based on the display rather than
                 // the headless renderer (which may be smaller).
-                val bounds = it.computeBounds(
-                    Rect(
-                    0,
-                    0,
-                        Resources.getSystem().displayMetrics.widthPixels,
-                        Resources.getSystem().displayMetrics.heightPixels
+                val bounds =
+                    it.computeBounds(
+                        Rect(
+                            0,
+                            0,
+                            Resources.getSystem().displayMetrics.widthPixels,
+                            Resources.getSystem().displayMetrics.heightPixels
+                        )
                     )
-                )
 
                 var prevData: ComplicationData? = null
                 val screenshotComplicationData = params.complicationData
                 if (screenshotComplicationData != null) {
                     prevData = it.renderer.getData()
-                    complicationSlotsManager.setComplicationDataUpdateSync(
+                    complicationSlotsManager.setComplicationDataUpdateForScreenshot(
                         params.complicationSlotId,
                         screenshotComplicationData.toApiComplicationData(),
                         instant
@@ -1303,12 +1305,13 @@
                         params.complicationSlotId
                     )
                     picture.endRecording()
-                    complicationBitmap = Api28CreateBitmapHelper.createBitmap(
-                        picture,
-                        bounds.width(),
-                        bounds.height(),
-                        Bitmap.Config.ARGB_8888
-                    )
+                    complicationBitmap =
+                        Api28CreateBitmapHelper.createBitmap(
+                            picture,
+                            bounds.width(),
+                            bounds.height(),
+                            Bitmap.Config.ARGB_8888
+                        )
                 } else {
                     complicationBitmap =
                         Bitmap.createBitmap(
@@ -1330,7 +1333,7 @@
                     // Restore previous ComplicationData & style if required.
                     if (prevData != null) {
                         val now = getNow()
-                        complicationSlotsManager.setComplicationDataUpdateSync(
+                        complicationSlotsManager.setComplicationDataUpdateForScreenshot(
                             params.complicationSlotId,
                             prevData,
                             now
@@ -1364,6 +1367,16 @@
         )
         writer.println("currentUserStyleRepository.schema=${currentUserStyleRepository.schema}")
         writer.println("editorObscuresWatchFace=$editorObscuresWatchFace")
+        writer.println("additionalContentDescriptionLabels:")
+        writer.increaseIndent()
+        for (label in renderer.additionalContentDescriptionLabels) {
+            if (Build.TYPE.equals("userdebug")) {
+                writer.println("${label.first}: ${label.second}")
+            } else {
+                writer.println("${label.first}: Redacted")
+            }
+        }
+        writer.decreaseIndent()
         overlayStyle.dump(writer)
         watchState.dump(writer)
         complicationSlotsManager.dump(writer)
@@ -1421,7 +1434,7 @@
 
             params.idAndComplicationDatumWireFormats?.let {
                 for (idAndData in it) {
-                    watchFaceImpl.complicationSlotsManager.setComplicationDataUpdateSync(
+                    watchFaceImpl.complicationSlotsManager.setComplicationDataUpdateForScreenshot(
                         idAndData.id,
                         idAndData.complicationData.toApiComplicationData(),
                         instant
@@ -1443,7 +1456,7 @@
             if (params.idAndComplicationDatumWireFormats != null) {
                 val now = watchFaceImpl.getNow()
                 for ((id, complicationData) in oldComplicationData) {
-                    watchFaceImpl.complicationSlotsManager.setComplicationDataUpdateSync(
+                    watchFaceImpl.complicationSlotsManager.setComplicationDataUpdateForScreenshot(
                         id,
                         complicationData,
                         now
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index 3a6c4ec..f131d12 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -70,6 +70,7 @@
 import androidx.wear.watchface.complications.data.ShortTextComplicationData
 import androidx.wear.watchface.complications.data.TimeDifferenceComplicationText
 import androidx.wear.watchface.complications.data.TimeDifferenceStyle
+import androidx.wear.watchface.complications.data.toApiComplicationData
 import androidx.wear.watchface.complications.rendering.CanvasComplicationDrawable
 import androidx.wear.watchface.complications.rendering.ComplicationDrawable
 import androidx.wear.watchface.control.HeadlessWatchFaceImpl
@@ -142,6 +143,7 @@
 import org.mockito.kotlin.verifyNoMoreInteractions
 import org.robolectric.Shadows.shadowOf
 import org.robolectric.annotation.Config
+import org.robolectric.shadows.ShadowBuild
 
 private const val INTERACTIVE_UPDATE_RATE_MS = 16L
 private const val LEFT_COMPLICATION_ID = 1000
@@ -728,6 +730,7 @@
 
     @Before
     public fun setUp() {
+        ShadowBuild.setType("userdebug")
         Assume.assumeTrue("This test suite assumes API 26", Build.VERSION.SDK_INT >= 26)
 
         `when`(handler.getLooper()).thenReturn(Looper.myLooper())
@@ -758,9 +761,11 @@
                  (TODO: b/264994539) - Explicitly releasing the mSurfaceControl field,
                  accessed via reflection. Remove when a proper fix is found
                 */
-                val mSurfaceControlObject: Field = WatchFaceService.EngineWrapper::class
-                    .java.superclass // android.service.wallpaper.WallpaperService$Engine
-                    .getDeclaredField("mSurfaceControl")
+                val mSurfaceControlObject: Field =
+                    WatchFaceService.EngineWrapper::class
+                        .java
+                        .superclass // android.service.wallpaper.WallpaperService$Engine
+                        .getDeclaredField("mSurfaceControl")
                 mSurfaceControlObject.isAccessible = true
                 (mSurfaceControlObject.get(engineWrapper) as SurfaceControl).release()
             }
@@ -1317,11 +1322,7 @@
                 null
             )
         verify(tapListener)
-            .onTapEvent(
-                TapType.UP,
-                TapEvent(10, 200, Instant.ofEpochMilli(looperTimeMillis)),
-                null
-            )
+            .onTapEvent(TapType.UP, TapEvent(10, 200, Instant.ofEpochMilli(looperTimeMillis)), null)
     }
 
     @Test
@@ -2858,6 +2859,48 @@
 
     @Test
     @Config(sdk = [Build.VERSION_CODES.O_MR1])
+    public fun updateComplicationData_appendsToHistory() {
+        initWallpaperInteractiveWatchFaceInstance(
+            complicationSlots = listOf(leftComplication)
+        )
+        // Validate that the history is initially empty.
+        assertThat(leftComplication.complicationHistory!!.iterator().asSequence().toList())
+            .isEmpty()
+        val longTextComplication =
+            WireComplicationData.Builder(WireComplicationData.TYPE_LONG_TEXT)
+                .setLongText(WireComplicationText.plainText("TYPE_LONG_TEXT"))
+                .build()
+
+        interactiveWatchFaceInstance.updateComplicationData(
+            listOf(IdAndComplicationDataWireFormat(LEFT_COMPLICATION_ID, longTextComplication))
+        )
+
+        assertThat(leftComplication.complicationHistory.toList().map { it.complicationData })
+            .containsExactly(longTextComplication.toApiComplicationData())
+    }
+
+    @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
+    public fun setComplicationDataUpdateForScreenshot_doesNotAppendToHistory() {
+        initWallpaperInteractiveWatchFaceInstance(
+            complicationSlots = listOf(leftComplication)
+        )
+
+        complicationSlotsManager.setComplicationDataUpdateForScreenshot(
+            LEFT_COMPLICATION_ID,
+            WireComplicationData.Builder(WireComplicationData.TYPE_LONG_TEXT)
+                .setLongText(WireComplicationText.plainText("TYPE_LONG_TEXT"))
+                .build()
+                .toApiComplicationData(),
+            Instant.now()
+        )
+
+        assertThat(leftComplication.complicationHistory!!.iterator().asSequence().toList())
+            .isEmpty()
+    }
+
+    @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun complicationCache() {
         val complicationCache = HashMap<String, ByteArray>()
         val instanceParams =
diff --git a/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java b/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java
index 676d286..74163e6 100644
--- a/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java
+++ b/window/extensions/extensions/src/main/java/androidx/window/extensions/WindowExtensions.java
@@ -18,12 +18,15 @@
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
+import android.app.Activity;
 import android.app.ActivityOptions;
+import android.content.Context;
 import android.os.IBinder;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.window.extensions.area.WindowAreaComponent;
+import androidx.window.extensions.core.util.function.Consumer;
 import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
 import androidx.window.extensions.embedding.ActivityStack;
 import androidx.window.extensions.embedding.SplitAttributes;
@@ -75,10 +78,13 @@
      * block.
      * The added APIs for Vendor API level 2 are:
      * <ul>
+     *     <li>{@link WindowAreaComponent#addRearDisplayStatusListener(Consumer)}</li>
+     *     <li>{@link WindowAreaComponent#startRearDisplaySession(Activity, Consumer)}</li>
      *     <li>{@link androidx.window.extensions.embedding.SplitPlaceholderRule.Builder#setFinishPrimaryWithPlaceholder(int)}</li>
      *     <li>{@link androidx.window.extensions.embedding.SplitAttributes}</li>
      *     <li>{@link ActivityEmbeddingComponent#setSplitAttributesCalculator(
      *      androidx.window.extensions.core.util.function.Function)}</li>
+     *     <li>{@link WindowLayoutComponent#addWindowLayoutInfoListener(Context, Consumer)}</li>
      * </ul>
      */
     @RestrictTo(LIBRARY_GROUP)
@@ -99,7 +105,11 @@
      *     <li>{@link ActivityEmbeddingComponent#updateSplitAttributes(IBinder, SplitAttributes)}
      *     </li>
      *     <li>{@link ActivityEmbeddingComponent#finishActivityStacks(Set)}</li>
-     *     <li>{@link androidx.window.extensions.area.WindowAreaComponent} APIs</li>
+     *     <li>{@link WindowAreaComponent#addRearDisplayPresentationStatusListener(Consumer)}</li>
+     *     <li>{@link WindowAreaComponent#startRearDisplayPresentationSession(Activity, Consumer)}
+     *     </li>
+     *     <li>{@link WindowAreaComponent#getRearDisplayMetrics()}</li>
+     *     <li>{@link WindowAreaComponent#getRearDisplayPresentation()}</li>
      * </ul>
      * </p>
      */
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExampleWindowInitializer.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExampleWindowInitializer.kt
index f9d8303..55425fa 100644
--- a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExampleWindowInitializer.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/ExampleWindowInitializer.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import androidx.startup.Initializer
+import androidx.window.WindowSdkExtensions
 import androidx.window.demo.R
 import androidx.window.demo.embedding.SplitAttributesToggleMainActivity.Companion.PREFIX_FULLSCREEN_TOGGLE
 import androidx.window.demo.embedding.SplitAttributesToggleMainActivity.Companion.PREFIX_PLACEHOLDER
@@ -55,7 +56,7 @@
 
     override fun create(context: Context): RuleController {
         SplitController.getInstance(context).apply {
-            if (isSplitAttributesCalculatorSupported()) {
+            if (WindowSdkExtensions.getInstance().extensionVersion >= 2) {
                 setSplitAttributesCalculator(::sampleSplitAttributesCalculator)
             }
         }
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityBase.java b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityBase.java
index faf792e..aaa751e 100644
--- a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityBase.java
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitActivityBase.java
@@ -39,6 +39,7 @@
 import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.core.util.Consumer;
+import androidx.window.WindowSdkExtensions;
 import androidx.window.demo.R;
 import androidx.window.demo.databinding.ActivitySplitActivityLayoutBinding;
 import androidx.window.embedding.ActivityEmbeddingController;
@@ -111,8 +112,7 @@
             }
             startActivity(new Intent(this, SplitActivityE.class), bundle);
         });
-        if (!ActivityEmbeddingOptions.isSetLaunchingActivityStackSupported(
-                ActivityOptions.makeBasic())) {
+        if (WindowSdkExtensions.getInstance().getExtensionVersion() < 3) {
             mViewBinding.setLaunchingEInActivityStack.setEnabled(false);
         }
         mViewBinding.launchF.setOnClickListener((View v) ->
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesToggleMainActivity.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesToggleMainActivity.kt
index 1021af3..d74e64d 100644
--- a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesToggleMainActivity.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesToggleMainActivity.kt
@@ -28,6 +28,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
+import androidx.window.WindowSdkExtensions
 import androidx.window.core.ExperimentalWindowApi
 import androidx.window.demo.R
 import androidx.window.demo.databinding.ActivitySplitAttributesTogglePrimaryActivityBinding
@@ -70,7 +71,7 @@
 
         activityEmbeddingController = ActivityEmbeddingController.getInstance(this)
 
-        if (!splitController.isSplitAttributesCalculatorSupported()) {
+        if (WindowSdkExtensions.getInstance().extensionVersion < 2) {
             placeholderFoldingAwareAttrsRadioButton.isEnabled = false
             viewBinding.placeholderUseCustomizedSplitAttributes.isEnabled = false
             splitRuleFoldingAwareAttrsRadioButton.isEnabled = false
@@ -212,11 +213,13 @@
 
     private suspend fun updateWarningMessages() {
         val warningMessages = StringBuilder().apply {
-            if (!splitController.isSplitAttributesCalculatorSupported()) {
+            val apiLevel = WindowSdkExtensions.getInstance().extensionVersion
+
+            if (apiLevel < 2) {
                 append(resources.getString(R.string.split_attributes_calculator_not_supported))
                 append("\n")
             }
-            if (!activityEmbeddingController.isFinishingActivityStacksSupported()) {
+            if (apiLevel < 3) {
                 append("Finishing secondary activities is not supported on this device!\n")
             }
             if (viewBinding.finishSecondaryActivitiesButton.isEnabled &&
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesTogglePrimaryActivity.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesTogglePrimaryActivity.kt
index 097da84..e7c62cd 100644
--- a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesTogglePrimaryActivity.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesTogglePrimaryActivity.kt
@@ -24,6 +24,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
+import androidx.window.WindowSdkExtensions
 import androidx.window.core.ExperimentalWindowApi
 import androidx.window.demo.R
 import androidx.window.embedding.ActivityStack
@@ -43,8 +44,7 @@
 
         viewBinding.rootSplitActivityLayout.setBackgroundColor(Color.parseColor("#e8f5e9"))
 
-        val isRuntimeApiSupported = activityEmbeddingController
-            .isFinishingActivityStacksSupported()
+        val isRuntimeApiSupported = WindowSdkExtensions.getInstance().extensionVersion >= 3
 
         secondaryActivityIntent = Intent(
             this,
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesToggleSecondaryActivity.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesToggleSecondaryActivity.kt
index eac978a..01104cb 100644
--- a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesToggleSecondaryActivity.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitAttributesToggleSecondaryActivity.kt
@@ -24,6 +24,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
+import androidx.window.WindowSdkExtensions
 import androidx.window.core.ExperimentalWindowApi
 import androidx.window.demo.R
 import androidx.window.demo.databinding.ActivitySplitAttributesToggleSecondaryActivityBinding
@@ -63,10 +64,8 @@
         activityEmbeddingController = ActivityEmbeddingController.getInstance(this)
         val splitAttributesCustomizationEnabled = demoActivityEmbeddingController
             .splitAttributesCustomizationEnabled.get()
-        splitAttributesUpdatesSupported =
-            splitController.isInvalidatingTopVisibleSplitAttributesSupported() &&
-                splitController.isUpdatingSplitAttributesSupported() &&
-                !splitAttributesCustomizationEnabled
+        splitAttributesUpdatesSupported = WindowSdkExtensions.getInstance().extensionVersion >= 3 &&
+            !splitAttributesCustomizationEnabled
 
         fullscreenToggleButton.apply {
             // Disable the toggle fullscreen feature if the device doesn't support runtime
diff --git a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityBase.kt b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityBase.kt
index 16aad4f..fcb0ddb 100644
--- a/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityBase.kt
+++ b/window/window-demos/demo/src/main/java/androidx/window/demo/embedding/SplitDeviceStateActivityBase.kt
@@ -28,6 +28,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
+import androidx.window.WindowSdkExtensions
 import androidx.window.demo.R
 import androidx.window.demo.databinding.ActivitySplitDeviceStateLayoutBinding
 import androidx.window.embedding.EmbeddingRule
@@ -63,6 +64,8 @@
     /** The last selected split rule id. */
     private var lastCheckedRuleId = 0
 
+    private val isCallbackSupported = WindowSdkExtensions.getInstance().extensionVersion >= 2
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         viewBinding = ActivitySplitDeviceStateLayoutBinding.inflate(layoutInflater)
@@ -103,7 +106,6 @@
         viewBinding.swapPrimarySecondaryPositionCheckBox.setOnCheckedChangeListener(this)
         viewBinding.launchActivityToSide.setOnClickListener(this)
 
-        val isCallbackSupported = splitController.isSplitAttributesCalculatorSupported()
         if (!isCallbackSupported) {
             // Disable the radioButtons that use SplitAttributesCalculator
             viewBinding.showFullscreenInPortraitRadioButton.isEnabled = false
@@ -295,7 +297,7 @@
             .setSplitType(SPLIT_TYPE_EXPAND)
             .build()
         var suggestToFinishItself = false
-        val isCallbackSupported = splitController.isSplitAttributesCalculatorSupported()
+
         // Traverse SplitInfos from the end because last SplitInfo has the highest z-order.
         for (info in newSplitInfos.reversed()) {
             if (info.contains(this@SplitDeviceStateActivityBase)) {
diff --git a/window/window-testing/src/main/java/androidx/window/testing/embedding/StubEmbeddingBackend.kt b/window/window-testing/src/main/java/androidx/window/testing/embedding/StubEmbeddingBackend.kt
index 78aca27..758b6d0 100644
--- a/window/window-testing/src/main/java/androidx/window/testing/embedding/StubEmbeddingBackend.kt
+++ b/window/window-testing/src/main/java/androidx/window/testing/embedding/StubEmbeddingBackend.kt
@@ -159,10 +159,6 @@
         TODO("Not yet implemented")
     }
 
-    override fun isSplitAttributesCalculatorSupported(): Boolean {
-        TODO("Not yet implemented")
-    }
-
     override fun getActivityStack(activity: Activity): ActivityStack? {
         TODO("Not yet implemented")
     }
@@ -178,10 +174,6 @@
         TODO("Not yet implemented")
     }
 
-    override fun isFinishActivityStacksSupported(): Boolean {
-        TODO("Not yet implemented")
-    }
-
     override fun invalidateTopVisibleSplitAttributes() {
         TODO("Not yet implemented")
     }
@@ -190,10 +182,6 @@
         TODO("Not yet implemented")
     }
 
-    override fun areSplitAttributesUpdatesSupported(): Boolean {
-        TODO("Not yet implemented")
-    }
-
     private fun validateRules(rules: Set<EmbeddingRule>) {
         val tags = HashSet<String>()
         rules.forEach { rule ->
diff --git a/window/window/api/1.2.0-beta03.txt b/window/window/api/1.2.0-beta03.txt
index 1930a35..4241d96 100644
--- a/window/window/api/1.2.0-beta03.txt
+++ b/window/window/api/1.2.0-beta03.txt
@@ -1,6 +1,11 @@
 // Signature format: 4.0
 package androidx.window {
 
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.PROPERTY}) public @interface RequiresWindowSdkExtension {
+    method public abstract int version();
+    property public abstract int version;
+  }
+
   public final class WindowProperties {
     field public static final androidx.window.WindowProperties INSTANCE;
     field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
@@ -10,6 +15,17 @@
     field public static final String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES = "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
   }
 
+  public abstract class WindowSdkExtensions {
+    method @IntRange(from=0L) public int getExtensionVersion();
+    method public static final androidx.window.WindowSdkExtensions getInstance();
+    property @IntRange(from=0L) public int extensionVersion;
+    field public static final androidx.window.WindowSdkExtensions.Companion Companion;
+  }
+
+  public static final class WindowSdkExtensions.Companion {
+    method public androidx.window.WindowSdkExtensions getInstance();
+  }
+
 }
 
 package androidx.window.area {
@@ -107,11 +123,10 @@
 package androidx.window.embedding {
 
   public final class ActivityEmbeddingController {
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
     method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public androidx.window.embedding.ActivityStack? getActivityStack(android.app.Activity activity);
     method public static androidx.window.embedding.ActivityEmbeddingController getInstance(android.content.Context context);
     method public boolean isActivityEmbedded(android.app.Activity activity);
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isFinishingActivityStacksSupported();
     field public static final androidx.window.embedding.ActivityEmbeddingController.Companion Companion;
   }
 
@@ -120,9 +135,8 @@
   }
 
   public final class ActivityEmbeddingOptions {
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static boolean isSetLaunchingActivityStackSupported(android.app.ActivityOptions);
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
   }
 
   public final class ActivityFilter {
@@ -245,16 +259,13 @@
   }
 
   public final class SplitController {
-    method public void clearSplitAttributesCalculator();
+    method @androidx.window.RequiresWindowSdkExtension(version=2) public void clearSplitAttributesCalculator();
     method public static androidx.window.embedding.SplitController getInstance(android.content.Context context);
     method public androidx.window.embedding.SplitController.SplitSupportStatus getSplitSupportStatus();
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isInvalidatingTopVisibleSplitAttributesSupported();
-    method public boolean isSplitAttributesCalculatorSupported();
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isUpdatingSplitAttributesSupported();
-    method public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
+    method @androidx.window.RequiresWindowSdkExtension(version=2) public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
     method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.window.embedding.SplitInfo>> splitInfoList(android.app.Activity activity);
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
     property public final androidx.window.embedding.SplitController.SplitSupportStatus splitSupportStatus;
     field public static final androidx.window.embedding.SplitController.Companion Companion;
   }
diff --git a/window/window/api/current.ignore b/window/window/api/current.ignore
deleted file mode 100644
index 2b9500cb..0000000
--- a/window/window/api/current.ignore
+++ /dev/null
@@ -1,7 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.window.embedding.SplitAttributesCalculatorParams#areDefaultConstraintsSatisfied():
-    Added method androidx.window.embedding.SplitAttributesCalculatorParams.areDefaultConstraintsSatisfied()
-
-
-RemovedMethod: androidx.window.embedding.SplitAttributesCalculatorParams#getAreDefaultConstraintsSatisfied():
-    Removed method androidx.window.embedding.SplitAttributesCalculatorParams.getAreDefaultConstraintsSatisfied()
diff --git a/window/window/api/current.txt b/window/window/api/current.txt
index 1930a35..4241d96 100644
--- a/window/window/api/current.txt
+++ b/window/window/api/current.txt
@@ -1,6 +1,11 @@
 // Signature format: 4.0
 package androidx.window {
 
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.PROPERTY}) public @interface RequiresWindowSdkExtension {
+    method public abstract int version();
+    property public abstract int version;
+  }
+
   public final class WindowProperties {
     field public static final androidx.window.WindowProperties INSTANCE;
     field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
@@ -10,6 +15,17 @@
     field public static final String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES = "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
   }
 
+  public abstract class WindowSdkExtensions {
+    method @IntRange(from=0L) public int getExtensionVersion();
+    method public static final androidx.window.WindowSdkExtensions getInstance();
+    property @IntRange(from=0L) public int extensionVersion;
+    field public static final androidx.window.WindowSdkExtensions.Companion Companion;
+  }
+
+  public static final class WindowSdkExtensions.Companion {
+    method public androidx.window.WindowSdkExtensions getInstance();
+  }
+
 }
 
 package androidx.window.area {
@@ -107,11 +123,10 @@
 package androidx.window.embedding {
 
   public final class ActivityEmbeddingController {
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
     method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public androidx.window.embedding.ActivityStack? getActivityStack(android.app.Activity activity);
     method public static androidx.window.embedding.ActivityEmbeddingController getInstance(android.content.Context context);
     method public boolean isActivityEmbedded(android.app.Activity activity);
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isFinishingActivityStacksSupported();
     field public static final androidx.window.embedding.ActivityEmbeddingController.Companion Companion;
   }
 
@@ -120,9 +135,8 @@
   }
 
   public final class ActivityEmbeddingOptions {
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static boolean isSetLaunchingActivityStackSupported(android.app.ActivityOptions);
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
   }
 
   public final class ActivityFilter {
@@ -245,16 +259,13 @@
   }
 
   public final class SplitController {
-    method public void clearSplitAttributesCalculator();
+    method @androidx.window.RequiresWindowSdkExtension(version=2) public void clearSplitAttributesCalculator();
     method public static androidx.window.embedding.SplitController getInstance(android.content.Context context);
     method public androidx.window.embedding.SplitController.SplitSupportStatus getSplitSupportStatus();
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isInvalidatingTopVisibleSplitAttributesSupported();
-    method public boolean isSplitAttributesCalculatorSupported();
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isUpdatingSplitAttributesSupported();
-    method public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
+    method @androidx.window.RequiresWindowSdkExtension(version=2) public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
     method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.window.embedding.SplitInfo>> splitInfoList(android.app.Activity activity);
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
     property public final androidx.window.embedding.SplitController.SplitSupportStatus splitSupportStatus;
     field public static final androidx.window.embedding.SplitController.Companion Companion;
   }
diff --git a/window/window/api/restricted_1.2.0-beta03.txt b/window/window/api/restricted_1.2.0-beta03.txt
index 1930a35..4241d96 100644
--- a/window/window/api/restricted_1.2.0-beta03.txt
+++ b/window/window/api/restricted_1.2.0-beta03.txt
@@ -1,6 +1,11 @@
 // Signature format: 4.0
 package androidx.window {
 
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.PROPERTY}) public @interface RequiresWindowSdkExtension {
+    method public abstract int version();
+    property public abstract int version;
+  }
+
   public final class WindowProperties {
     field public static final androidx.window.WindowProperties INSTANCE;
     field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
@@ -10,6 +15,17 @@
     field public static final String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES = "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
   }
 
+  public abstract class WindowSdkExtensions {
+    method @IntRange(from=0L) public int getExtensionVersion();
+    method public static final androidx.window.WindowSdkExtensions getInstance();
+    property @IntRange(from=0L) public int extensionVersion;
+    field public static final androidx.window.WindowSdkExtensions.Companion Companion;
+  }
+
+  public static final class WindowSdkExtensions.Companion {
+    method public androidx.window.WindowSdkExtensions getInstance();
+  }
+
 }
 
 package androidx.window.area {
@@ -107,11 +123,10 @@
 package androidx.window.embedding {
 
   public final class ActivityEmbeddingController {
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
     method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public androidx.window.embedding.ActivityStack? getActivityStack(android.app.Activity activity);
     method public static androidx.window.embedding.ActivityEmbeddingController getInstance(android.content.Context context);
     method public boolean isActivityEmbedded(android.app.Activity activity);
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isFinishingActivityStacksSupported();
     field public static final androidx.window.embedding.ActivityEmbeddingController.Companion Companion;
   }
 
@@ -120,9 +135,8 @@
   }
 
   public final class ActivityEmbeddingOptions {
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static boolean isSetLaunchingActivityStackSupported(android.app.ActivityOptions);
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
   }
 
   public final class ActivityFilter {
@@ -245,16 +259,13 @@
   }
 
   public final class SplitController {
-    method public void clearSplitAttributesCalculator();
+    method @androidx.window.RequiresWindowSdkExtension(version=2) public void clearSplitAttributesCalculator();
     method public static androidx.window.embedding.SplitController getInstance(android.content.Context context);
     method public androidx.window.embedding.SplitController.SplitSupportStatus getSplitSupportStatus();
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isInvalidatingTopVisibleSplitAttributesSupported();
-    method public boolean isSplitAttributesCalculatorSupported();
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isUpdatingSplitAttributesSupported();
-    method public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
+    method @androidx.window.RequiresWindowSdkExtension(version=2) public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
     method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.window.embedding.SplitInfo>> splitInfoList(android.app.Activity activity);
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
     property public final androidx.window.embedding.SplitController.SplitSupportStatus splitSupportStatus;
     field public static final androidx.window.embedding.SplitController.Companion Companion;
   }
diff --git a/window/window/api/restricted_current.ignore b/window/window/api/restricted_current.ignore
deleted file mode 100644
index 2b9500cb..0000000
--- a/window/window/api/restricted_current.ignore
+++ /dev/null
@@ -1,7 +0,0 @@
-// Baseline format: 1.0
-AddedMethod: androidx.window.embedding.SplitAttributesCalculatorParams#areDefaultConstraintsSatisfied():
-    Added method androidx.window.embedding.SplitAttributesCalculatorParams.areDefaultConstraintsSatisfied()
-
-
-RemovedMethod: androidx.window.embedding.SplitAttributesCalculatorParams#getAreDefaultConstraintsSatisfied():
-    Removed method androidx.window.embedding.SplitAttributesCalculatorParams.getAreDefaultConstraintsSatisfied()
diff --git a/window/window/api/restricted_current.txt b/window/window/api/restricted_current.txt
index 1930a35..4241d96 100644
--- a/window/window/api/restricted_current.txt
+++ b/window/window/api/restricted_current.txt
@@ -1,6 +1,11 @@
 // Signature format: 4.0
 package androidx.window {
 
+  @kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY_GETTER, kotlin.annotation.AnnotationTarget.PROPERTY_SETTER, kotlin.annotation.AnnotationTarget.CONSTRUCTOR, kotlin.annotation.AnnotationTarget.FIELD, kotlin.annotation.AnnotationTarget.PROPERTY}) public @interface RequiresWindowSdkExtension {
+    method public abstract int version();
+    property public abstract int version;
+  }
+
   public final class WindowProperties {
     field public static final androidx.window.WindowProperties INSTANCE;
     field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
@@ -10,6 +15,17 @@
     field public static final String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES = "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
   }
 
+  public abstract class WindowSdkExtensions {
+    method @IntRange(from=0L) public int getExtensionVersion();
+    method public static final androidx.window.WindowSdkExtensions getInstance();
+    property @IntRange(from=0L) public int extensionVersion;
+    field public static final androidx.window.WindowSdkExtensions.Companion Companion;
+  }
+
+  public static final class WindowSdkExtensions.Companion {
+    method public androidx.window.WindowSdkExtensions getInstance();
+  }
+
 }
 
 package androidx.window.area {
@@ -107,11 +123,10 @@
 package androidx.window.embedding {
 
   public final class ActivityEmbeddingController {
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void finishActivityStacks(java.util.Set<androidx.window.embedding.ActivityStack> activityStacks);
     method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public androidx.window.embedding.ActivityStack? getActivityStack(android.app.Activity activity);
     method public static androidx.window.embedding.ActivityEmbeddingController getInstance(android.content.Context context);
     method public boolean isActivityEmbedded(android.app.Activity activity);
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isFinishingActivityStacksSupported();
     field public static final androidx.window.embedding.ActivityEmbeddingController.Companion Companion;
   }
 
@@ -120,9 +135,8 @@
   }
 
   public final class ActivityEmbeddingOptions {
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static boolean isSetLaunchingActivityStackSupported(android.app.ActivityOptions);
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.app.Activity activity);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public static android.app.ActivityOptions setLaunchingActivityStack(android.app.ActivityOptions, android.content.Context context, androidx.window.embedding.ActivityStack activityStack);
   }
 
   public final class ActivityFilter {
@@ -245,16 +259,13 @@
   }
 
   public final class SplitController {
-    method public void clearSplitAttributesCalculator();
+    method @androidx.window.RequiresWindowSdkExtension(version=2) public void clearSplitAttributesCalculator();
     method public static androidx.window.embedding.SplitController getInstance(android.content.Context context);
     method public androidx.window.embedding.SplitController.SplitSupportStatus getSplitSupportStatus();
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isInvalidatingTopVisibleSplitAttributesSupported();
-    method public boolean isSplitAttributesCalculatorSupported();
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public boolean isUpdatingSplitAttributesSupported();
-    method public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void invalidateTopVisibleSplitAttributes();
+    method @androidx.window.RequiresWindowSdkExtension(version=2) public void setSplitAttributesCalculator(kotlin.jvm.functions.Function1<? super androidx.window.embedding.SplitAttributesCalculatorParams,androidx.window.embedding.SplitAttributes> calculator);
     method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.window.embedding.SplitInfo>> splitInfoList(android.app.Activity activity);
-    method @SuppressCompatibility @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
+    method @SuppressCompatibility @androidx.window.RequiresWindowSdkExtension(version=3) @androidx.window.core.ExperimentalWindowApi public void updateSplitAttributes(androidx.window.embedding.SplitInfo splitInfo, androidx.window.embedding.SplitAttributes splitAttributes);
     property public final androidx.window.embedding.SplitController.SplitSupportStatus splitSupportStatus;
     field public static final androidx.window.embedding.SplitController.Companion Companion;
   }
diff --git a/window/window/samples/src/main/java/androidx.window.samples/WindowSdkExtensionsSamples.kt b/window/window/samples/src/main/java/androidx.window.samples/WindowSdkExtensionsSamples.kt
new file mode 100644
index 0000000..f4049d1
--- /dev/null
+++ b/window/window/samples/src/main/java/androidx.window.samples/WindowSdkExtensionsSamples.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.samples
+
+import androidx.annotation.Sampled
+import androidx.window.RequiresWindowSdkExtension
+import androidx.window.WindowSdkExtensions
+import androidx.window.embedding.SplitAttributes
+import androidx.window.embedding.SplitAttributesCalculatorParams
+import androidx.window.embedding.SplitController
+import androidx.window.samples.embedding.context
+
+@Sampled
+fun checkWindowSdkExtensionsVersion() {
+    // For example, SplitController#setSplitAttributesCalculator requires extension version 2.
+    // Callers must check the current extension version before invoking the API, or exception will
+    // be thrown.
+    if (WindowSdkExtensions.getInstance().extensionVersion >= 2) {
+        SplitController.getInstance(context).setSplitAttributesCalculator(splitAttributesCalculator)
+    }
+}
+
+val splitAttributesCalculator = { _: SplitAttributesCalculatorParams ->
+    SplitAttributes.Builder().build()
+}
+
+@Sampled
+fun annotateRequiresWindowSdkExtension() {
+    // Given that there's an API required Window SDK Extension version 3
+    @RequiresWindowSdkExtension(3)
+    fun coolFeature() {}
+
+    // Developers can use @RequiresWindowSdkExtension to annotate their own functions to document
+    // the required minimum API level.
+    @RequiresWindowSdkExtension(3)
+    fun useCoolFeatureNoCheck() {
+        coolFeature()
+    }
+
+    // Then users know they should wrap the function with version check
+    if (WindowSdkExtensions.getInstance().extensionVersion >= 3) {
+        useCoolFeatureNoCheck()
+    }
+}
diff --git a/window/window/src/main/java/androidx/window/RequiresWindowSdkExtension.kt b/window/window/src/main/java/androidx/window/RequiresWindowSdkExtension.kt
new file mode 100644
index 0000000..d112c94
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/RequiresWindowSdkExtension.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window
+
+import androidx.annotation.IntRange
+
+// TODO(b/292738295): Provide lint checks for RequiresWindowSdkExtension
+/**
+ * Denotes that the annotated element must only be used if
+ * [WindowSdkExtensions.extensionVersion] is greater than or equal to the given [version].
+ * Please see code sample linked below for usages.
+ *
+ * Calling the API that requires a higher level than the device's current level may lead to
+ * exceptions or unexpected results.
+ *
+ * @param version the minimum required [WindowSdkExtensions] version of the denoted target
+ *
+ * @sample androidx.window.samples.annotateRequiresWindowSdkExtension
+ */
+@MustBeDocumented
+@Retention(value = AnnotationRetention.BINARY)
+@Target(
+    AnnotationTarget.CLASS,
+    AnnotationTarget.FUNCTION,
+    AnnotationTarget.PROPERTY_GETTER,
+    AnnotationTarget.PROPERTY_SETTER,
+    AnnotationTarget.CONSTRUCTOR,
+    AnnotationTarget.FIELD,
+    AnnotationTarget.PROPERTY,
+)
+annotation class RequiresWindowSdkExtension(
+    /** The minimum required [WindowSdkExtensions] version of the denoted target */
+    @IntRange(from = 1)
+    val version: Int
+)
diff --git a/window/window/src/main/java/androidx/window/WindowSdkExtensions.kt b/window/window/src/main/java/androidx/window/WindowSdkExtensions.kt
new file mode 100644
index 0000000..26f0a6d
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/WindowSdkExtensions.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window
+
+import androidx.annotation.IntRange
+import androidx.window.core.ExtensionsUtil
+
+/**
+ * This class provides information about the extension SDK versions for window features present on
+ * this device. Use [extensionVersion] to get the version of the extension. The extension version
+ * advances as the platform evolves and new APIs are added, so is suitable to use for determining
+ * API availability at runtime.
+ *
+ * Window Manager Jetpack APIs that require window SDK extensions support are denoted with
+ * [RequiresWindowSdkExtension]. The caller must check whether the device's extension version is
+ * greater than or equal to the minimum level reported in [RequiresWindowSdkExtension].
+ *
+ * @sample androidx.window.samples.checkWindowSdkExtensionsVersion
+ */
+abstract class WindowSdkExtensions internal constructor() {
+
+    /**
+     * Reports the device's extension version
+     *
+     * When Window SDK Extensions is not present on the device, the extension version will be 0.
+     */
+    @get: IntRange(from = 0)
+    open val extensionVersion: Int = ExtensionsUtil.safeVendorApiLevel
+
+    /**
+     * Checks the [extensionVersion] and throws [UnsupportedOperationException] if the minimum
+     * [version] is not satisfied.
+     *
+     * @param version The minimum required extension version of the targeting API.
+     * @throws UnsupportedOperationException if the minimum [version] is not satisfied.
+     */
+    internal fun requireExtensionVersion(@IntRange(from = 1) version: Int) {
+        if (extensionVersion < version) {
+            throw UnsupportedOperationException("This API requires extension version " +
+                "$version, but the device is on $extensionVersion")
+        }
+    }
+
+    companion object {
+        /** Returns a [WindowSdkExtensions] instance. */
+        @JvmStatic
+        fun getInstance(): WindowSdkExtensions {
+            return decorator.decorate(object : WindowSdkExtensions() {})
+        }
+
+        private var decorator: WindowSdkExtensionsDecorator = EmptyDecoratorWindowSdk
+
+        internal fun overrideDecorator(overridingDecorator: WindowSdkExtensionsDecorator) {
+            decorator = overridingDecorator
+        }
+
+        internal fun reset() {
+            decorator = EmptyDecoratorWindowSdk
+        }
+    }
+}
+
+internal interface WindowSdkExtensionsDecorator {
+    /** Returns a [WindowSdkExtensions] instance. */
+    fun decorate(windowSdkExtensions: WindowSdkExtensions): WindowSdkExtensions
+}
+
+private object EmptyDecoratorWindowSdk : WindowSdkExtensionsDecorator {
+    override fun decorate(windowSdkExtensions: WindowSdkExtensions): WindowSdkExtensions =
+        windowSdkExtensions
+}
diff --git a/window/window/src/main/java/androidx/window/area/WindowAreaController.kt b/window/window/src/main/java/androidx/window/area/WindowAreaController.kt
index ae2500c..d671726 100644
--- a/window/window/src/main/java/androidx/window/area/WindowAreaController.kt
+++ b/window/window/src/main/java/androidx/window/area/WindowAreaController.kt
@@ -21,6 +21,7 @@
 import android.os.Build
 import android.util.Log
 import androidx.annotation.RestrictTo
+import androidx.window.WindowSdkExtensions
 import androidx.window.area.WindowAreaInfo.Type.Companion.TYPE_REAR_FACING
 import androidx.window.area.utils.DeviceUtils
 import androidx.window.core.BuildConfig
@@ -41,6 +42,9 @@
     /**
      * [Flow] of the list of current [WindowAreaInfo]s that are currently available to be interacted
      * with.
+     *
+     * If [WindowSdkExtensions.extensionVersion] is less than 2,  the flow will return
+     * empty [WindowAreaInfo] list flow.
      */
     val windowAreaInfos: Flow<List<WindowAreaInfo>>
 
diff --git a/window/window/src/main/java/androidx/window/core/ExtensionsUtil.kt b/window/window/src/main/java/androidx/window/core/ExtensionsUtil.kt
index d12062c..ec92f1e 100644
--- a/window/window/src/main/java/androidx/window/core/ExtensionsUtil.kt
+++ b/window/window/src/main/java/androidx/window/core/ExtensionsUtil.kt
@@ -17,20 +17,19 @@
 package androidx.window.core
 
 import android.util.Log
-import androidx.annotation.VisibleForTesting
+import androidx.annotation.IntRange
 import androidx.window.core.VerificationMode.LOG
 import androidx.window.extensions.WindowExtensionsProvider
 
 internal object ExtensionsUtil {
 
     private val TAG = ExtensionsUtil::class.simpleName
-    private var overrideVendorApiLevel: Int? = null
 
+    @get:IntRange(from = 0)
     val safeVendorApiLevel: Int
         get() {
             return try {
-                overrideVendorApiLevel
-                    ?: WindowExtensionsProvider.getWindowExtensions().vendorApiLevel
+                WindowExtensionsProvider.getWindowExtensions().vendorApiLevel
             } catch (e: NoClassDefFoundError) {
                 if (BuildConfig.verificationMode == LOG) {
                     Log.d(TAG, "Embedding extension version not found")
@@ -43,14 +42,4 @@
                 0
             }
         }
-
-    @VisibleForTesting
-    internal fun setOverrideVendorApiLevel(apiLevel: Int) {
-        overrideVendorApiLevel = apiLevel
-    }
-
-    @VisibleForTesting
-    internal fun resetOverrideVendorApiLevel() {
-        overrideVendorApiLevel = null
-    }
 }
diff --git a/window/window/src/main/java/androidx/window/embedding/ActivityEmbeddingController.kt b/window/window/src/main/java/androidx/window/embedding/ActivityEmbeddingController.kt
index 8adffae..bbbd045 100644
--- a/window/window/src/main/java/androidx/window/embedding/ActivityEmbeddingController.kt
+++ b/window/window/src/main/java/androidx/window/embedding/ActivityEmbeddingController.kt
@@ -20,9 +20,13 @@
 import android.app.ActivityOptions
 import android.content.Context
 import android.os.IBinder
+import androidx.window.RequiresWindowSdkExtension
+import androidx.window.WindowSdkExtensions
 import androidx.window.core.ExperimentalWindowApi
 
-/** The controller that allows checking the current [Activity] embedding status. */
+/**
+ * The controller that allows checking the current [Activity] embedding status.
+ */
 class ActivityEmbeddingController internal constructor(private val backend: EmbeddingBackend) {
     /**
      * Checks if the [activity] is embedded and its presentation may be customized by the host
@@ -41,7 +45,7 @@
      *
      * @param activity The [Activity] to check.
      * @return the [ActivityStack] that this [activity] is part of, or `null` if there is no such
-     *   [ActivityStack].
+     * [ActivityStack].
      */
     @ExperimentalWindowApi
     fun getActivityStack(activity: Activity): ActivityStack? =
@@ -53,6 +57,7 @@
      * @param options The [android.app.ActivityOptions] to be updated.
      * @param token The token of the [ActivityStack] to be set.
      */
+    @RequiresWindowSdkExtension(3)
     internal fun setLaunchingActivityStack(
         options: ActivityOptions,
         token: IBinder
@@ -73,28 +78,20 @@
      * will be expanded to fill the parent task container. This is useful to expand the primary
      * container as the sample linked below shows.
      *
-     * **Note** that it's caller's responsibility to check whether this API is supported by calling
-     * [isFinishingActivityStacksSupported]. If not, an alternative approach to finishing all
-     * containers above a particular activity can be to launch it again with flag
-     * [android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP].
+     * **Note** that it's caller's responsibility to check whether this API is supported by checking
+     * [WindowSdkExtensions.extensionVersion] is greater than or equal to 3. If not, an alternative
+     * approach to finishing all containers above a particular activity can be to launch it again
+     * with flag [android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP].
      *
      * @param activityStacks The set of [ActivityStack] to be finished.
-     * @throws UnsupportedOperationException if this device doesn't support this API and
-     * [isFinishingActivityStacksSupported] returns `false`.
+     * @throws UnsupportedOperationException if extension version is less than 3.
      * @sample androidx.window.samples.embedding.expandPrimaryContainer
      */
     @ExperimentalWindowApi
-    fun finishActivityStacks(activityStacks: Set<ActivityStack>) =
+    @RequiresWindowSdkExtension(3)
+    fun finishActivityStacks(activityStacks: Set<ActivityStack>) {
         backend.finishActivityStacks(activityStacks)
-
-    /**
-     * Checks whether [finishActivityStacks] is supported.
-     *
-     * @return `true` if [finishActivityStacks] is supported on the device, `false` otherwise.
-     */
-    @ExperimentalWindowApi
-    fun isFinishingActivityStacksSupported(): Boolean =
-        backend.isFinishActivityStacksSupported()
+    }
 
     companion object {
         /**
diff --git a/window/window/src/main/java/androidx/window/embedding/ActivityEmbeddingOptions.kt b/window/window/src/main/java/androidx/window/embedding/ActivityEmbeddingOptions.kt
index 190ffa3..8f5b66b 100644
--- a/window/window/src/main/java/androidx/window/embedding/ActivityEmbeddingOptions.kt
+++ b/window/window/src/main/java/androidx/window/embedding/ActivityEmbeddingOptions.kt
@@ -20,34 +20,29 @@
 import android.app.Activity
 import android.app.ActivityOptions
 import android.content.Context
+import androidx.window.RequiresWindowSdkExtension
+import androidx.window.WindowSdkExtensions
 import androidx.window.core.ExperimentalWindowApi
-import androidx.window.core.ExtensionsUtil
-import androidx.window.extensions.WindowExtensions
 
 /**
  * Sets the launching [ActivityStack] to the given [android.app.ActivityOptions].
  *
  * If the device doesn't support setting launching, [UnsupportedOperationException] will be thrown.
- * @see isSetLaunchingActivityStackSupported
  *
  * @param context The [android.content.Context] that is going to be used for launching
  * activity with this [android.app.ActivityOptions], which is usually be the [android.app.Activity]
  * of the app that hosts the task.
  * @param activityStack The target [ActivityStack] for launching.
- * @throws UnsupportedOperationException if this device doesn't support this API.
+ * @throws UnsupportedOperationException if [WindowSdkExtensions.extensionVersion] is less than 3.
  */
 @ExperimentalWindowApi
+@RequiresWindowSdkExtension(3)
 fun ActivityOptions.setLaunchingActivityStack(
     context: Context,
     activityStack: ActivityStack
 ): ActivityOptions = let {
-    if (!isSetLaunchingActivityStackSupported()) {
-        throw UnsupportedOperationException("#setLaunchingActivityStack is not " +
-            "supported on the device.")
-    } else {
-        ActivityEmbeddingController.getInstance(context)
-            .setLaunchingActivityStack(this, activityStack.token)
-    }
+    ActivityEmbeddingController.getInstance(context)
+        .setLaunchingActivityStack(this, activityStack.token)
 }
 
 /**
@@ -57,13 +52,12 @@
  *
  * If the device doesn't support setting launching or no available [ActivityStack]
  * can be found from the given [activity], [UnsupportedOperationException] will be thrown.
- * @see isSetLaunchingActivityStackSupported
  *
  * @param activity The existing [android.app.Activity] on the target [ActivityStack].
- * @throws UnsupportedOperationException if this device doesn't support this API or no
- * available [ActivityStack] can be found.
+ * @throws UnsupportedOperationException if [WindowSdkExtensions.extensionVersion] is less than 3.
  */
 @ExperimentalWindowApi
+@RequiresWindowSdkExtension(3)
 fun ActivityOptions.setLaunchingActivityStack(activity: Activity): ActivityOptions {
     val activityStack =
         ActivityEmbeddingController.getInstance(activity).getActivityStack(activity)
@@ -74,12 +68,3 @@
             "The given activity may not be embedded.")
     }
 }
-
-/**
- * Return `true` if the [setLaunchingActivityStack] APIs is supported and can be used
- * to set the launching [ActivityStack]. Otherwise, return `false`.
- */
-@ExperimentalWindowApi
-fun ActivityOptions.isSetLaunchingActivityStackSupported(): Boolean {
-    return ExtensionsUtil.safeVendorApiLevel >= WindowExtensions.VENDOR_API_LEVEL_3
-}
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt
index ce56396..2462f83 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingAdapter.kt
@@ -24,7 +24,7 @@
 import android.util.LayoutDirection
 import android.util.Pair as AndroidPair
 import android.view.WindowMetrics
-import androidx.window.core.ExtensionsUtil
+import androidx.window.WindowSdkExtensions
 import androidx.window.core.PredicateAdapter
 import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.BOTTOM_TO_TOP
 import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.LEFT_TO_RIGHT
@@ -36,7 +36,6 @@
 import androidx.window.embedding.SplitAttributes.SplitType.Companion.SPLIT_TYPE_EXPAND
 import androidx.window.embedding.SplitAttributes.SplitType.Companion.SPLIT_TYPE_HINGE
 import androidx.window.embedding.SplitAttributes.SplitType.Companion.ratio
-import androidx.window.extensions.WindowExtensions
 import androidx.window.extensions.core.util.function.Function
 import androidx.window.extensions.core.util.function.Predicate
 import androidx.window.extensions.embedding.ActivityRule as OEMActivityRule
@@ -63,7 +62,8 @@
 internal class EmbeddingAdapter(
     private val predicateAdapter: PredicateAdapter
 ) {
-    private val vendorApiLevel = ExtensionsUtil.safeVendorApiLevel
+    private val vendorApiLevel
+        get() = WindowSdkExtensions.getInstance().extensionVersion
     private val api1Impl = VendorApiLevel1Impl(predicateAdapter)
     private val api2Impl = VendorApiLevel2Impl()
 
@@ -73,8 +73,8 @@
 
     private fun translate(splitInfo: OEMSplitInfo): SplitInfo {
         return when (vendorApiLevel) {
-            WindowExtensions.VENDOR_API_LEVEL_1 -> api1Impl.translateCompat(splitInfo)
-            WindowExtensions.VENDOR_API_LEVEL_2 -> api2Impl.translateCompat(splitInfo)
+            1 -> api1Impl.translateCompat(splitInfo)
+            2 -> api2Impl.translateCompat(splitInfo)
             else -> {
                 val primaryActivityStack = splitInfo.primaryActivityStack
                 val secondaryActivityStack = splitInfo.secondaryActivityStack
@@ -152,7 +152,7 @@
         rule: SplitPairRule,
         predicateClass: Class<*>
     ): OEMSplitPairRule {
-        if (vendorApiLevel < WindowExtensions.VENDOR_API_LEVEL_2) {
+        if (vendorApiLevel < 2) {
             return api1Impl.translateSplitPairRuleCompat(context, rule, predicateClass)
         } else {
             val activitiesPairPredicate =
@@ -194,7 +194,7 @@
     }
 
     fun translateSplitAttributes(splitAttributes: SplitAttributes): OEMSplitAttributes {
-        require(vendorApiLevel >= WindowExtensions.VENDOR_API_LEVEL_2)
+        require(vendorApiLevel >= 2)
         // To workaround the "unused" error in ktlint. It is necessary to translate SplitAttributes
         // from WM Jetpack version to WM extension version.
         return androidx.window.extensions.embedding.SplitAttributes.Builder()
@@ -215,7 +215,7 @@
     }
 
     private fun translateSplitType(splitType: SplitType): OEMSplitType {
-        require(vendorApiLevel >= WindowExtensions.VENDOR_API_LEVEL_2)
+        require(vendorApiLevel >= 2)
         return when (splitType) {
             SPLIT_TYPE_HINGE -> OEMSplitType.HingeSplitType(
                 translateSplitType(SPLIT_TYPE_EQUAL)
@@ -238,7 +238,7 @@
         rule: SplitPlaceholderRule,
         predicateClass: Class<*>
     ): OEMSplitPlaceholderRule {
-        if (vendorApiLevel < WindowExtensions.VENDOR_API_LEVEL_2) {
+        if (vendorApiLevel < 2) {
             return api1Impl.translateSplitPlaceholderRuleCompat(
                 context,
                 rule,
@@ -285,7 +285,7 @@
         rule: ActivityRule,
         predicateClass: Class<*>
     ): OEMActivityRule {
-        if (vendorApiLevel < WindowExtensions.VENDOR_API_LEVEL_2) {
+        if (vendorApiLevel < 2) {
             return api1Impl.translateActivityRuleCompat(rule, predicateClass)
         } else {
             val activityPredicate = Predicate<Activity> { activity ->
@@ -317,6 +317,7 @@
         }.toSet()
     }
 
+    /** Provides backward compatibility for Window extensions with API level 2 */
     private inner class VendorApiLevel2Impl {
         fun translateCompat(splitInfo: OEMSplitInfo): SplitInfo {
             val primaryActivityStack = splitInfo.primaryActivityStack
@@ -342,15 +343,13 @@
     }
 
     /**
-     * Provides backward compatibility for Window extensions with
-     * [WindowExtensions.VENDOR_API_LEVEL_1]
-     * @see WindowExtensions.getVendorApiLevel
+     * Provides backward compatibility for [WindowSdkExtensions] version 1
      */
     // Suppress deprecation because this object is to provide backward compatibility.
     @Suppress("DEPRECATION")
     private inner class VendorApiLevel1Impl(val predicateAdapter: PredicateAdapter) {
         /**
-         * Obtains [SplitAttributes] from [OEMSplitInfo] with [WindowExtensions.VENDOR_API_LEVEL_1]
+         * Obtains [SplitAttributes] from [OEMSplitInfo] with [WindowSdkExtensions] version 1
          */
         fun getSplitAttributesCompat(splitInfo: OEMSplitInfo): SplitAttributes =
             SplitAttributes.Builder()
@@ -472,9 +471,8 @@
             }
 
         /**
-         * Returns `true` if `attrs` is compatible with [WindowExtensions.VENDOR_API_LEVEL_1] and
-         * doesn't use the new features introduced in [WindowExtensions.VENDOR_API_LEVEL_2] or
-         * higher.
+         * Returns `true` if `attrs` is compatible with vendor API level 1 and
+         * doesn't use the new features introduced in vendor API level 2 or higher.
          */
         private fun isSplitAttributesSupported(attrs: SplitAttributes) =
             attrs.splitType.value in 0.0..1.0 && attrs.splitType.value != 1.0f &&
@@ -519,12 +517,12 @@
     internal companion object {
         /**
          * The default token of [SplitInfo], which provides compatibility for device prior to
-         * [WindowExtensions.VENDOR_API_LEVEL_3]
+         * vendor API level 3
          */
         val INVALID_SPLIT_INFO_TOKEN = Binder()
         /**
          * The default token of [ActivityStack], which provides compatibility for device prior to
-         * [WindowExtensions.VENDOR_API_LEVEL_3]
+         * vendor API level 3
          */
         val INVALID_ACTIVITY_STACK_TOKEN = Binder()
     }
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingBackend.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingBackend.kt
index 22b6333..c1b09d0 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingBackend.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingBackend.kt
@@ -22,6 +22,7 @@
 import android.os.IBinder
 import androidx.annotation.RestrictTo
 import androidx.core.util.Consumer
+import androidx.window.RequiresWindowSdkExtension
 import java.util.concurrent.Executor
 
 /**
@@ -50,28 +51,28 @@
 
     fun isActivityEmbedded(activity: Activity): Boolean
 
+    @RequiresWindowSdkExtension(2)
     fun setSplitAttributesCalculator(
         calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
     )
 
+    @RequiresWindowSdkExtension(2)
     fun clearSplitAttributesCalculator()
 
-    fun isSplitAttributesCalculatorSupported(): Boolean
-
     fun getActivityStack(activity: Activity): ActivityStack?
 
+    @RequiresWindowSdkExtension(3)
     fun setLaunchingActivityStack(options: ActivityOptions, token: IBinder): ActivityOptions
 
+    @RequiresWindowSdkExtension(3)
     fun finishActivityStacks(activityStacks: Set<ActivityStack>)
 
-    fun isFinishActivityStacksSupported(): Boolean
-
+    @RequiresWindowSdkExtension(3)
     fun invalidateTopVisibleSplitAttributes()
 
+    @RequiresWindowSdkExtension(3)
     fun updateSplitAttributes(splitInfo: SplitInfo, splitAttributes: SplitAttributes)
 
-    fun areSplitAttributesUpdatesSupported(): Boolean
-
     companion object {
 
         private var decorator: (EmbeddingBackend) -> EmbeddingBackend =
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt
index 2c543f3..a4b7166 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingCompat.kt
@@ -21,6 +21,8 @@
 import android.content.Context
 import android.os.IBinder
 import android.util.Log
+import androidx.window.RequiresWindowSdkExtension
+import androidx.window.WindowSdkExtensions
 import androidx.window.core.BuildConfig
 import androidx.window.core.ConsumerAdapter
 import androidx.window.core.ExtensionsUtil
@@ -28,7 +30,6 @@
 import androidx.window.embedding.EmbeddingInterfaceCompat.EmbeddingCallbackInterface
 import androidx.window.embedding.SplitController.SplitSupportStatus.Companion.SPLIT_AVAILABLE
 import androidx.window.extensions.WindowExtensions.VENDOR_API_LEVEL_2
-import androidx.window.extensions.WindowExtensions.VENDOR_API_LEVEL_3
 import androidx.window.extensions.WindowExtensionsProvider
 import androidx.window.extensions.core.util.function.Consumer
 import androidx.window.extensions.embedding.ActivityEmbeddingComponent
@@ -39,7 +40,7 @@
  * Adapter implementation for different historical versions of activity embedding OEM interface in
  * [ActivityEmbeddingComponent]. Only supports the single current version in this implementation.
  */
-internal class EmbeddingCompat constructor(
+internal class EmbeddingCompat(
     private val embeddingExtension: ActivityEmbeddingComponent,
     private val adapter: EmbeddingAdapter,
     private val consumerAdapter: ConsumerAdapter,
@@ -92,70 +93,59 @@
         return embeddingExtension.isActivityEmbedded(activity)
     }
 
+    @RequiresWindowSdkExtension(2)
     override fun setSplitAttributesCalculator(
         calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
     ) {
-        if (!isSplitAttributesCalculatorSupported()) {
-            throw UnsupportedOperationException("#setSplitAttributesCalculator is not supported " +
-                "on the device.")
-        }
+        WindowSdkExtensions.getInstance().requireExtensionVersion(2)
+
         embeddingExtension.setSplitAttributesCalculator(
             adapter.translateSplitAttributesCalculator(calculator)
         )
     }
 
+    @RequiresWindowSdkExtension(2)
     override fun clearSplitAttributesCalculator() {
-        if (!isSplitAttributesCalculatorSupported()) {
-            throw UnsupportedOperationException("#clearSplitAttributesCalculator is not " +
-                "supported on the device.")
-        }
+        WindowSdkExtensions.getInstance().requireExtensionVersion(2)
+
         embeddingExtension.clearSplitAttributesCalculator()
     }
 
-    override fun isSplitAttributesCalculatorSupported(): Boolean =
-        ExtensionsUtil.safeVendorApiLevel >= VENDOR_API_LEVEL_2
-
+    @RequiresWindowSdkExtension(3)
     override fun finishActivityStacks(activityStacks: Set<ActivityStack>) {
-        if (!isFinishActivityStacksSupported()) {
-            throw UnsupportedOperationException("#finishActivityStacks is not " +
-                "supported on the device.")
-        }
+        WindowSdkExtensions.getInstance().requireExtensionVersion(3)
+
         val stackTokens = activityStacks.mapTo(mutableSetOf()) { it.token }
         embeddingExtension.finishActivityStacks(stackTokens)
     }
 
-    override fun isFinishActivityStacksSupported(): Boolean =
-        ExtensionsUtil.safeVendorApiLevel >= VENDOR_API_LEVEL_3
-
+    @RequiresWindowSdkExtension(3)
     override fun invalidateTopVisibleSplitAttributes() {
-        if (!areSplitAttributesUpdatesSupported()) {
-            throw UnsupportedOperationException("#invalidateTopVisibleSplitAttributes is not " +
-                "supported on the device.")
-        }
+        WindowSdkExtensions.getInstance().requireExtensionVersion(3)
+
         embeddingExtension.invalidateTopVisibleSplitAttributes()
     }
 
+    @RequiresWindowSdkExtension(3)
     override fun updateSplitAttributes(
         splitInfo: SplitInfo,
         splitAttributes: SplitAttributes
     ) {
-        if (!areSplitAttributesUpdatesSupported()) {
-            throw UnsupportedOperationException("#updateSplitAttributes is not supported on the " +
-                "device.")
-        }
+        WindowSdkExtensions.getInstance().requireExtensionVersion(3)
+
         embeddingExtension.updateSplitAttributes(
             splitInfo.token,
             adapter.translateSplitAttributes(splitAttributes)
         )
     }
 
-    override fun areSplitAttributesUpdatesSupported(): Boolean =
-        ExtensionsUtil.safeVendorApiLevel >= VENDOR_API_LEVEL_3
-
+    @RequiresWindowSdkExtension(3)
     override fun setLaunchingActivityStack(
         options: ActivityOptions,
         token: IBinder
     ): ActivityOptions {
+        WindowSdkExtensions.getInstance().requireExtensionVersion(3)
+
         return embeddingExtension.setLaunchingActivityStack(options, token)
     }
 
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingInterfaceCompat.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingInterfaceCompat.kt
index ccfba1d..e176a02 100644
--- a/window/window/src/main/java/androidx/window/embedding/EmbeddingInterfaceCompat.kt
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingInterfaceCompat.kt
@@ -19,6 +19,7 @@
 import android.app.Activity
 import android.app.ActivityOptions
 import android.os.IBinder
+import androidx.window.RequiresWindowSdkExtension
 import androidx.window.extensions.embedding.ActivityEmbeddingComponent
 
 /**
@@ -37,23 +38,23 @@
 
     fun isActivityEmbedded(activity: Activity): Boolean
 
+    @RequiresWindowSdkExtension(2)
     fun setSplitAttributesCalculator(
         calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
     )
 
+    @RequiresWindowSdkExtension(2)
     fun clearSplitAttributesCalculator()
 
-    fun isSplitAttributesCalculatorSupported(): Boolean
-
+    @RequiresWindowSdkExtension(3)
     fun setLaunchingActivityStack(options: ActivityOptions, token: IBinder): ActivityOptions
 
+    @RequiresWindowSdkExtension(3)
     fun finishActivityStacks(activityStacks: Set<ActivityStack>)
 
-    fun isFinishActivityStacksSupported(): Boolean
-
+    @RequiresWindowSdkExtension(3)
     fun invalidateTopVisibleSplitAttributes()
 
+    @RequiresWindowSdkExtension(3)
     fun updateSplitAttributes(splitInfo: SplitInfo, splitAttributes: SplitAttributes)
-
-    fun areSplitAttributesUpdatesSupported(): Boolean
 }
diff --git a/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt b/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
index c7f4c3b..a53a98c 100644
--- a/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
+++ b/window/window/src/main/java/androidx/window/embedding/ExtensionEmbeddingBackend.kt
@@ -29,6 +29,7 @@
 import androidx.annotation.VisibleForTesting
 import androidx.collection.ArraySet
 import androidx.core.util.Consumer
+import androidx.window.RequiresWindowSdkExtension
 import androidx.window.WindowProperties
 import androidx.window.core.BuildConfig
 import androidx.window.core.ConsumerAdapter
@@ -336,6 +337,7 @@
         return embeddingExtension?.isActivityEmbedded(activity) ?: false
     }
 
+    @RequiresWindowSdkExtension(2)
     override fun setSplitAttributesCalculator(
         calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
     ) {
@@ -344,15 +346,13 @@
         }
     }
 
+    @RequiresWindowSdkExtension(2)
     override fun clearSplitAttributesCalculator() {
         globalLock.withLock {
             embeddingExtension?.clearSplitAttributesCalculator()
         }
     }
 
-    override fun isSplitAttributesCalculatorSupported(): Boolean =
-        embeddingExtension?.isSplitAttributesCalculatorSupported() ?: false
-
     override fun getActivityStack(activity: Activity): ActivityStack? {
         globalLock.withLock {
             val lastInfo: List<SplitInfo> = splitInfoEmbeddingCallback.lastInfo ?: return null
@@ -371,22 +371,23 @@
         }
     }
 
+    @RequiresWindowSdkExtension(3)
     override fun setLaunchingActivityStack(
         options: ActivityOptions,
         token: IBinder
     ): ActivityOptions = embeddingExtension?.setLaunchingActivityStack(options, token) ?: options
 
+    @RequiresWindowSdkExtension(3)
     override fun finishActivityStacks(activityStacks: Set<ActivityStack>) {
         embeddingExtension?.finishActivityStacks(activityStacks)
     }
 
-    override fun isFinishActivityStacksSupported(): Boolean =
-        embeddingExtension?.isFinishActivityStacksSupported() ?: false
-
+    @RequiresWindowSdkExtension(3)
     override fun invalidateTopVisibleSplitAttributes() {
         embeddingExtension?.invalidateTopVisibleSplitAttributes()
     }
 
+    @RequiresWindowSdkExtension(3)
     override fun updateSplitAttributes(
         splitInfo: SplitInfo,
         splitAttributes: SplitAttributes
@@ -394,8 +395,6 @@
         embeddingExtension?.updateSplitAttributes(splitInfo, splitAttributes)
     }
 
-    override fun areSplitAttributesUpdatesSupported(): Boolean =
-        embeddingExtension?.areSplitAttributesUpdatesSupported() ?: false
     @RequiresApi(31)
     private object Api31Impl {
         @DoNotInline
diff --git a/window/window/src/main/java/androidx/window/embedding/RuleController.kt b/window/window/src/main/java/androidx/window/embedding/RuleController.kt
index 192f3ec..8943726 100644
--- a/window/window/src/main/java/androidx/window/embedding/RuleController.kt
+++ b/window/window/src/main/java/androidx/window/embedding/RuleController.kt
@@ -27,6 +27,7 @@
  * - [setRules]
  * - [parseRules]
  * - [clearRules]
+ * - [getRules]
  *
  * **Note** that this class is recommended to be configured in [androidx.startup.Initializer] or
  * [android.app.Application.onCreate], so that the rules are applied early in the application
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitAttributes.kt b/window/window/src/main/java/androidx/window/embedding/SplitAttributes.kt
index 3e22b1a..ac627fc 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitAttributes.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitAttributes.kt
@@ -21,6 +21,7 @@
 import androidx.annotation.IntRange
 import androidx.annotation.RestrictTo
 import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
+import androidx.window.WindowSdkExtensions
 import androidx.window.core.SpecificationComputer.Companion.startSpecification
 import androidx.window.core.VerificationMode
 import androidx.window.embedding.SplitAttributes.LayoutDirection.Companion.LOCALE
@@ -307,7 +308,8 @@
              * <img width="70%" height="70%" src="/images/guide/topics/large-screens/activity-embedding/reference-docs/a_to_a_b_ttb.png" alt="Activity A starts activity B to the bottom."/>
              *
              * If the horizontal layout direction is not supported on the
-             * device, layout direction falls back to `LOCALE`.
+             * device that [WindowSdkExtensions.extensionVersion] is less than 2, layout direction
+             * falls back to `LOCALE`.
              *
              * See also [layoutDirection].
              */
@@ -323,7 +325,8 @@
              * <img width="70%" height="70%" src="/images/guide/topics/large-screens/activity-embedding/reference-docs/a_to_a_b_btt.png" alt="Activity A starts activity B to the top."/>
              *
              * If the horizontal layout direction is not supported on the
-             * device, layout direction falls back to `LOCALE`.
+             * device that [WindowSdkExtensions.extensionVersion] is less than 2, layout direction
+             * falls back to `LOCALE`.
              *
              * See also [layoutDirection].
              */
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitController.kt b/window/window/src/main/java/androidx/window/embedding/SplitController.kt
index e478ff7..063bc0c 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitController.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitController.kt
@@ -19,7 +19,9 @@
 import android.app.Activity
 import android.content.Context
 import androidx.core.util.Consumer
+import androidx.window.RequiresWindowSdkExtension
 import androidx.window.WindowProperties
+import androidx.window.WindowSdkExtensions
 import androidx.window.core.ExperimentalWindowApi
 import androidx.window.layout.WindowMetrics
 import kotlinx.coroutines.channels.awaitClose
@@ -27,17 +29,17 @@
 import kotlinx.coroutines.flow.callbackFlow
 
 /**
-* The controller class that gets information about the currently active activity
-* splits and provides interaction points to customize the splits and form new
-* splits.
-*
-* A split is a pair of containers that host activities in the same or different
-* processes, combined under the same parent window of the hosting task.
-*
-* A pair of activities can be put into a split by providing a static or runtime
-* split rule and then launching the activities in the same task using
-* [Activity.startActivity()][android.app.Activity.startActivity].
-*/
+ * The controller class that gets information about the currently active activity
+ * splits and provides interaction points to customize the splits and form new
+ * splits.
+ *
+ * A split is a pair of containers that host activities in the same or different
+ * processes, combined under the same parent window of the hosting task.
+ *
+ * A pair of activities can be put into a split by providing a static or runtime
+ * split rule and then launching the activities in the same task using
+ * [Activity.startActivity()][android.app.Activity.startActivity].
+ */
 class SplitController internal constructor(private val embeddingBackend: EmbeddingBackend) {
 
     /**
@@ -86,8 +88,8 @@
     /**
      * Sets or replaces the previously registered [SplitAttributes] calculator.
      *
-     * **Note** that it's callers' responsibility to check if this API is supported by calling
-     * [isSplitAttributesCalculatorSupported] before using the this API. It is suggested to always
+     * **Note** that it's callers' responsibility to check if this API is supported by checking
+     * [WindowSdkExtensions.extensionVersion] before using the this API. It is suggested to always
      * set meaningful [SplitRule.defaultSplitAttributes] in case this API is not supported on some
      * devices.
      *
@@ -124,9 +126,10 @@
      * @sample androidx.window.samples.embedding.splitAttributesCalculatorSample
      * @param calculator the function to calculate [SplitAttributes] based on the
      * [SplitAttributesCalculatorParams]. It will replace the previously set if it exists.
-     * @throws UnsupportedOperationException if [isSplitAttributesCalculatorSupported] reports
-     * `false`
+     * @throws UnsupportedOperationException if [WindowSdkExtensions.extensionVersion]
+     *                                       is less than 2.
      */
+    @RequiresWindowSdkExtension(2)
     fun setSplitAttributesCalculator(
         calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
     ) {
@@ -135,19 +138,17 @@
 
     /**
      * Clears the callback previously set by [setSplitAttributesCalculator].
-     * The caller **must** make sure [isSplitAttributesCalculatorSupported] before invoking.
+     * The caller **must** make sure if [WindowSdkExtensions.extensionVersion] is greater than
+     * or equal to 2.
      *
-     * @throws UnsupportedOperationException if [isSplitAttributesCalculatorSupported] reports
-     * `false`
+     * @throws UnsupportedOperationException if [WindowSdkExtensions.extensionVersion]
+     *                                       is less than 2.
      */
+    @RequiresWindowSdkExtension(2)
     fun clearSplitAttributesCalculator() {
         embeddingBackend.clearSplitAttributesCalculator()
     }
 
-    /** Returns whether [setSplitAttributesCalculator] is supported or not. */
-    fun isSplitAttributesCalculatorSupported(): Boolean =
-        embeddingBackend.isSplitAttributesCalculatorSupported()
-
     /**
      * Triggers a [SplitAttributes] update callback for the current topmost and visible split layout
      * if there is one. This method can be used when a change to the split presentation originates
@@ -160,23 +161,14 @@
      *
      * The call will be ignored if there is no visible split.
      *
-     * @throws UnsupportedOperationException if the device doesn't support this API.
+     * @throws UnsupportedOperationException if [WindowSdkExtensions.extensionVersion]
+     *                                       is less than 3.
      */
     @ExperimentalWindowApi
-    fun invalidateTopVisibleSplitAttributes() =
+    @RequiresWindowSdkExtension(3)
+    fun invalidateTopVisibleSplitAttributes() {
         embeddingBackend.invalidateTopVisibleSplitAttributes()
-
-    /**
-     * Checks whether [invalidateTopVisibleSplitAttributes] is supported on the device.
-     *
-     * Invoking these APIs if the feature is not supported would trigger an
-     * [UnsupportedOperationException].
-     * @return `true` if the runtime APIs to update [SplitAttributes] are supported and can be
-     * called safely, `false` otherwise.
-     */
-    @ExperimentalWindowApi
-    fun isInvalidatingTopVisibleSplitAttributesSupported(): Boolean =
-        embeddingBackend.areSplitAttributesUpdatesSupported()
+    }
 
     /**
      * Updates the [SplitAttributes] of a split pair. This is an alternative to using
@@ -198,23 +190,14 @@
      *
      * @param splitInfo the split pair to update
      * @param splitAttributes the [SplitAttributes] to be applied
-     * @throws UnsupportedOperationException if this device doesn't support this API
+     * @throws UnsupportedOperationException if [WindowSdkExtensions.extensionVersion]
+     *                                       is less than 3.
      */
     @ExperimentalWindowApi
-    fun updateSplitAttributes(splitInfo: SplitInfo, splitAttributes: SplitAttributes) =
+    @RequiresWindowSdkExtension(3)
+    fun updateSplitAttributes(splitInfo: SplitInfo, splitAttributes: SplitAttributes) {
         embeddingBackend.updateSplitAttributes(splitInfo, splitAttributes)
-
-    /**
-     * Checks whether [updateSplitAttributes] is supported on the device.
-     *
-     * Invoking these APIs if the feature is not supported would trigger an
-     * [UnsupportedOperationException].
-     * @return `true` if the runtime APIs to update [SplitAttributes] are supported and can be
-     * called safely, `false` otherwise.
-     */
-    @ExperimentalWindowApi
-    fun isUpdatingSplitAttributesSupported(): Boolean =
-        embeddingBackend.areSplitAttributesUpdatesSupported()
+    }
 
     /**
      * A class to determine if activity splits with Activity Embedding are currently available.
diff --git a/window/window/src/main/java/androidx/window/layout/WindowInfoTracker.kt b/window/window/src/main/java/androidx/window/layout/WindowInfoTracker.kt
index 60eb71e..06ca64f 100644
--- a/window/window/src/main/java/androidx/window/layout/WindowInfoTracker.kt
+++ b/window/window/src/main/java/androidx/window/layout/WindowInfoTracker.kt
@@ -23,6 +23,7 @@
 import androidx.annotation.RestrictTo
 import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
 import androidx.annotation.UiContext
+import androidx.window.WindowSdkExtensions
 import androidx.window.core.ConsumerAdapter
 import androidx.window.layout.adapter.WindowBackend
 import androidx.window.layout.adapter.extensions.ExtensionWindowBackend
@@ -53,6 +54,9 @@
      * Obtaining a [WindowInfoTracker] through [WindowInfoTracker.getOrCreate] guarantees having a
      * default implementation for this method.
      *
+     * If the passed [context] is not an [Activity] and [WindowSdkExtensions.extensionVersion]
+     * is less than 2, the flow will return empty [WindowLayoutInfo] list flow.
+     *
      * @param context a [UiContext] such as an [Activity], an [InputMethodService], or an instance
      * created via [Context.createWindowContext] that listens to configuration changes.
      * @see WindowLayoutInfo
diff --git a/window/window/src/test/java/androidx/window/StubWindowSdkExtensions.kt b/window/window/src/test/java/androidx/window/StubWindowSdkExtensions.kt
new file mode 100644
index 0000000..3ffe46f
--- /dev/null
+++ b/window/window/src/test/java/androidx/window/StubWindowSdkExtensions.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window
+
+import androidx.annotation.IntRange
+
+internal class StubWindowSdkExtensions : WindowSdkExtensions() {
+    override val extensionVersion: Int
+        get() = overrideVersion
+
+    private var overrideVersion: Int = 0
+
+    internal fun overrideExtensionVersion(@IntRange(from = 0) version: Int) {
+        overrideVersion = version
+    }
+}
diff --git a/window/window/src/test/java/androidx/window/WindowSdkExtensionsRule.kt b/window/window/src/test/java/androidx/window/WindowSdkExtensionsRule.kt
new file mode 100644
index 0000000..a2826e2
--- /dev/null
+++ b/window/window/src/test/java/androidx/window/WindowSdkExtensionsRule.kt
@@ -0,0 +1,67 @@
+/**
+*
+* Copyright 2023 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package androidx.window
+
+import androidx.annotation.IntRange
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Test rule for overriding [WindowSdkExtensions] properties.
+ *
+ * This should mainly be used for validating the behavior with a simplified version of
+ * [WindowSdkExtensions] in unit tests.
+ * For on-device Android tests, it's highly suggested to respect
+ * the device's [WindowSdkExtensions.extensionVersion]. Overriding the real device's is error-prone,
+ * and may lead to unexpected behavior.
+ */
+class WindowSdkExtensionsRule : TestRule {
+
+    private val mStubWindowSdkExtensions = StubWindowSdkExtensions()
+
+    override fun apply(
+        @Suppress("InvalidNullabilityOverride") // JUnit missing annotations
+        base: Statement,
+        @Suppress("InvalidNullabilityOverride") // JUnit missing annotations
+        description: Description
+    ): Statement {
+        return object : Statement() {
+            override fun evaluate() {
+                WindowSdkExtensions.overrideDecorator(object : WindowSdkExtensionsDecorator {
+                    override fun decorate(windowSdkExtensions: WindowSdkExtensions):
+                        WindowSdkExtensions = mStubWindowSdkExtensions
+                })
+                try {
+                    base.evaluate()
+                } finally {
+                    WindowSdkExtensions.reset()
+                }
+            }
+        }
+    }
+
+    /**
+     * Overrides [WindowSdkExtensions.extensionVersion] for testing.
+     *
+     * @param version The extension version to override
+     */
+    fun overrideExtensionVersion(@IntRange(from = 0) version: Int) {
+        mStubWindowSdkExtensions.overrideExtensionVersion(version)
+    }
+}
diff --git a/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingControllerTest.kt b/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingControllerTest.kt
index c4cf0b5..6d950f6 100644
--- a/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingControllerTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingControllerTest.kt
@@ -19,6 +19,7 @@
 import android.app.Activity
 import android.content.Context
 import android.os.Binder
+import androidx.window.core.ExperimentalWindowApi
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -61,7 +62,7 @@
     }
 
     @Test
-    @OptIn(androidx.window.core.ExperimentalWindowApi::class)
+    @OptIn(ExperimentalWindowApi::class)
     fun testGetActivityStack() {
         val activityStack = ActivityStack(listOf(), true, Binder())
         whenever(mockEmbeddingBackend.getActivityStack(mockActivity)).thenReturn(activityStack)
@@ -70,19 +71,7 @@
     }
 
     @Test
-    @OptIn(androidx.window.core.ExperimentalWindowApi::class)
-    fun testIsFinishingActivityStacksSupported() {
-        whenever(mockEmbeddingBackend.isFinishActivityStacksSupported()).thenReturn(true)
-
-        assertTrue(activityEmbeddingController.isFinishingActivityStacksSupported())
-
-        whenever(mockEmbeddingBackend.isFinishActivityStacksSupported()).thenReturn(false)
-
-        assertFalse(activityEmbeddingController.isFinishingActivityStacksSupported())
-    }
-
-    @Test
-    @OptIn(androidx.window.core.ExperimentalWindowApi::class)
+    @OptIn(ExperimentalWindowApi::class)
     fun testFinishActivityStacks() {
         val activityStacks: Set<ActivityStack> = mock()
         activityEmbeddingController.finishActivityStacks(activityStacks)
@@ -91,7 +80,7 @@
     }
 
     @Test
-    @OptIn(androidx.window.core.ExperimentalWindowApi::class)
+    @OptIn(ExperimentalWindowApi::class)
     fun testGetInstance() {
         EmbeddingBackend.overrideDecorator(object : EmbeddingBackendDecorator {
             override fun decorate(embeddingBackend: EmbeddingBackend): EmbeddingBackend =
diff --git a/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingOptionsTest.kt b/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingOptionsTest.kt
index d9c2127..aea6821 100644
--- a/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingOptionsTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/ActivityEmbeddingOptionsTest.kt
@@ -19,18 +19,12 @@
 import android.app.Activity
 import android.app.ActivityOptions
 import android.content.Context
-import androidx.window.core.ExtensionsUtil
-import androidx.window.extensions.WindowExtensions
+import androidx.window.core.ExperimentalWindowApi
 import org.junit.After
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertThrows
-import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
-import org.mockito.kotlin.any
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
@@ -38,9 +32,8 @@
  * The unit tests for activity embedding extension functions to [ActivityOptions]
  *
  * @see [ActivityOptions.setLaunchingActivityStack]
- * @see [ActivityOptions.isSetLaunchingActivityStackSupported]
  */
-@OptIn(androidx.window.core.ExperimentalWindowApi::class)
+@OptIn(ExperimentalWindowApi::class)
 class ActivityEmbeddingOptionsTest {
 
     private lateinit var mockEmbeddingBackend: EmbeddingBackend
@@ -64,15 +57,11 @@
             override fun decorate(embeddingBackend: EmbeddingBackend): EmbeddingBackend =
                 mockEmbeddingBackend
         })
-
-        // ActivityEmbeddingOptions is only supported since level 3
-        ExtensionsUtil.setOverrideVendorApiLevel(WindowExtensions.VENDOR_API_LEVEL_3)
     }
 
     @After
     fun tearDown() {
         EmbeddingBackend.reset()
-        ExtensionsUtil.resetOverrideVendorApiLevel()
     }
 
     @Test
@@ -90,25 +79,4 @@
         verify(mockEmbeddingBackend).setLaunchingActivityStack(
             mockActivityOptions, mockActivityStack.token)
     }
-
-    @Test
-    fun testSetLaunchingActivityStack_unsupportedApiLevel() {
-        ExtensionsUtil.setOverrideVendorApiLevel(WindowExtensions.VENDOR_API_LEVEL_2)
-
-        assertThrows(UnsupportedOperationException::class.java) {
-            mockActivityOptions.setLaunchingActivityStack(mockActivity, mockActivityStack)
-        }
-        verify(mockEmbeddingBackend, never()).setLaunchingActivityStack(any(), any())
-    }
-
-    @Test
-    fun testIsSetLaunchingActivityStackSupported() {
-        ExtensionsUtil.setOverrideVendorApiLevel(WindowExtensions.VENDOR_API_LEVEL_2)
-
-        assertFalse(mockActivityOptions.isSetLaunchingActivityStackSupported())
-
-        ExtensionsUtil.setOverrideVendorApiLevel(WindowExtensions.VENDOR_API_LEVEL_3)
-
-        assertTrue(mockActivityOptions.isSetLaunchingActivityStackSupported())
-    }
 }
diff --git a/window/window/src/test/java/androidx/window/embedding/RequiresWindowSdkExtensionTests.kt b/window/window/src/test/java/androidx/window/embedding/RequiresWindowSdkExtensionTests.kt
new file mode 100644
index 0000000..7b0b830
--- /dev/null
+++ b/window/window/src/test/java/androidx/window/embedding/RequiresWindowSdkExtensionTests.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.embedding
+
+import android.app.ActivityOptions
+import android.content.Context
+import android.os.Binder
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.window.RequiresWindowSdkExtension
+import androidx.window.WindowSdkExtensions
+import androidx.window.WindowSdkExtensionsRule
+import androidx.window.core.ConsumerAdapter
+import androidx.window.core.PredicateAdapter
+import androidx.window.embedding.EmbeddingAdapter.Companion.INVALID_ACTIVITY_STACK_TOKEN
+import androidx.window.embedding.EmbeddingAdapter.Companion.INVALID_SPLIT_INFO_TOKEN
+import androidx.window.extensions.core.util.function.Function
+import androidx.window.extensions.embedding.ActivityEmbeddingComponent
+import androidx.window.extensions.embedding.SplitAttributes as OemSplitAttributes
+import androidx.window.extensions.embedding.SplitAttributesCalculatorParams as OemSplitAttributesCalculatorParams
+import org.junit.After
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+/**
+ * Verifies the behavior of [RequiresWindowSdkExtension]
+ * - If the [WindowSdkExtensions.extensionVersion] is greater than or equal to the minimum required
+ *   version denoted in [RequiresWindowSdkExtension.version], the denoted API must be called
+ *   successfully
+ * - Otherwise, [UnsupportedOperationException] must be thrown.
+ */
+@RequiresApi(Build.VERSION_CODES.M) // To call ActivityOptions.makeBasic()
+class RequiresWindowSdkExtensionTests {
+
+    @get:Rule
+    val testRule = WindowSdkExtensionsRule()
+
+    @Mock
+    private lateinit var embeddingExtension: ActivityEmbeddingComponent
+    @Mock
+    private lateinit var classLoader: ClassLoader
+    @Mock
+    private lateinit var applicationContext: Context
+    @Mock
+    private lateinit var activityOptions: ActivityOptions
+
+    private lateinit var mockAnnotations: AutoCloseable
+    private lateinit var embeddingCompat: EmbeddingCompat
+
+    @Before
+    fun setUp() {
+        mockAnnotations = MockitoAnnotations.openMocks(this)
+        embeddingCompat = EmbeddingCompat(
+            embeddingExtension,
+            EmbeddingAdapter(PredicateAdapter(classLoader)),
+            ConsumerAdapter(classLoader),
+            applicationContext
+        )
+
+        doReturn(activityOptions).whenever(embeddingExtension).setLaunchingActivityStack(
+            activityOptions,
+            INVALID_ACTIVITY_STACK_TOKEN
+        )
+    }
+
+    @After
+    fun tearDown() {
+        mockAnnotations.close()
+    }
+
+    @Test
+    fun testVendorApiLevel1() {
+        testRule.overrideExtensionVersion(1)
+
+        assertThrows(UnsupportedOperationException::class.java) {
+            embeddingCompat.setSplitAttributesCalculator { _ -> TEST_SPLIT_ATTRIBUTES }
+        }
+        verify(embeddingExtension, never()).setSplitAttributesCalculator(any())
+
+        assertThrows(UnsupportedOperationException::class.java) {
+            embeddingCompat.clearSplitAttributesCalculator()
+        }
+        verify(embeddingExtension, never()).clearSplitAttributesCalculator()
+
+        assertThrows(UnsupportedOperationException::class.java) {
+            embeddingCompat.setLaunchingActivityStack(activityOptions, Binder())
+        }
+        verify(embeddingExtension, never()).setLaunchingActivityStack(any(), any())
+
+        assertThrows(UnsupportedOperationException::class.java) {
+            embeddingCompat.finishActivityStacks(emptySet())
+        }
+        verify(embeddingExtension, never()).finishActivityStacks(any())
+
+        assertThrows(UnsupportedOperationException::class.java) {
+            embeddingCompat.updateSplitAttributes(TEST_SPLIT_INFO, TEST_SPLIT_ATTRIBUTES)
+        }
+        verify(embeddingExtension, never()).updateSplitAttributes(any(), any())
+
+        assertThrows(UnsupportedOperationException::class.java) {
+            embeddingCompat.invalidateTopVisibleSplitAttributes()
+        }
+        verify(embeddingExtension, never()).invalidateTopVisibleSplitAttributes()
+    }
+
+    @Test
+    fun testVendorApiLevel2() {
+        testRule.overrideExtensionVersion(2)
+
+        embeddingCompat.setSplitAttributesCalculator { _ -> TEST_SPLIT_ATTRIBUTES }
+        verify(embeddingExtension).setSplitAttributesCalculator(
+            any<Function<OemSplitAttributesCalculatorParams, OemSplitAttributes>>()
+        )
+
+        embeddingCompat.clearSplitAttributesCalculator()
+        verify(embeddingExtension).clearSplitAttributesCalculator()
+
+        assertThrows(UnsupportedOperationException::class.java) {
+            embeddingCompat.setLaunchingActivityStack(activityOptions, INVALID_ACTIVITY_STACK_TOKEN)
+        }
+        verify(embeddingExtension, never()).setLaunchingActivityStack(any(), any())
+
+        assertThrows(UnsupportedOperationException::class.java) {
+            embeddingCompat.finishActivityStacks(emptySet())
+        }
+        verify(embeddingExtension, never()).finishActivityStacks(any())
+
+        assertThrows(UnsupportedOperationException::class.java) {
+            embeddingCompat.updateSplitAttributes(TEST_SPLIT_INFO, TEST_SPLIT_ATTRIBUTES)
+        }
+        verify(embeddingExtension, never()).updateSplitAttributes(any(), any())
+
+        assertThrows(UnsupportedOperationException::class.java) {
+            embeddingCompat.invalidateTopVisibleSplitAttributes()
+        }
+        verify(embeddingExtension, never()).invalidateTopVisibleSplitAttributes()
+    }
+
+    @Test
+    fun testVendorApiLevel3() {
+        testRule.overrideExtensionVersion(3)
+
+        embeddingCompat.setSplitAttributesCalculator { _ -> TEST_SPLIT_ATTRIBUTES }
+        verify(embeddingExtension).setSplitAttributesCalculator(
+            any<Function<OemSplitAttributesCalculatorParams, OemSplitAttributes>>()
+        )
+
+        embeddingCompat.clearSplitAttributesCalculator()
+        verify(embeddingExtension).clearSplitAttributesCalculator()
+
+        embeddingCompat.setLaunchingActivityStack(activityOptions, INVALID_ACTIVITY_STACK_TOKEN)
+
+        verify(embeddingExtension).setLaunchingActivityStack(
+            activityOptions,
+            INVALID_ACTIVITY_STACK_TOKEN
+        )
+
+        embeddingCompat.finishActivityStacks(emptySet())
+        verify(embeddingExtension).finishActivityStacks(emptySet())
+
+        embeddingCompat.updateSplitAttributes(TEST_SPLIT_INFO, TEST_SPLIT_ATTRIBUTES)
+        verify(embeddingExtension).updateSplitAttributes(
+            INVALID_SPLIT_INFO_TOKEN,
+            OemSplitAttributes.Builder().build()
+        )
+
+        embeddingCompat.invalidateTopVisibleSplitAttributes()
+        verify(embeddingExtension).invalidateTopVisibleSplitAttributes()
+    }
+
+    companion object {
+        private val TEST_SPLIT_INFO = SplitInfo(
+            ActivityStack(emptyList(), isEmpty = true, INVALID_ACTIVITY_STACK_TOKEN),
+            ActivityStack(emptyList(), isEmpty = true, INVALID_ACTIVITY_STACK_TOKEN),
+            SplitAttributes.Builder().build(),
+            INVALID_SPLIT_INFO_TOKEN,
+        )
+
+        private val TEST_SPLIT_ATTRIBUTES = SplitAttributes.Builder().build()
+    }
+}
diff --git a/window/window/src/test/java/androidx/window/embedding/SplitControllerTest.kt b/window/window/src/test/java/androidx/window/embedding/SplitControllerTest.kt
index 2188e0e..390c681 100644
--- a/window/window/src/test/java/androidx/window/embedding/SplitControllerTest.kt
+++ b/window/window/src/test/java/androidx/window/embedding/SplitControllerTest.kt
@@ -26,7 +26,6 @@
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertEquals
-import org.junit.Assert.assertTrue
 import org.junit.Test
 import org.mockito.kotlin.any
 import org.mockito.kotlin.doAnswer
@@ -78,9 +77,6 @@
     fun test_splitAttributesCalculator_delegates() {
         val mockCalculator = mock<(SplitAttributesCalculatorParams) -> SplitAttributes>()
 
-        whenever(mockBackend.isSplitAttributesCalculatorSupported()).thenReturn(true)
-        assertTrue(splitController.isSplitAttributesCalculatorSupported())
-
         splitController.setSplitAttributesCalculator(mockCalculator)
         verify(mockBackend).setSplitAttributesCalculator(mockCalculator)
 
@@ -90,9 +86,6 @@
 
     @Test
     fun test_updateSplitAttribute_delegates() {
-        whenever(mockBackend.areSplitAttributesUpdatesSupported()).thenReturn(true)
-        assertTrue(splitController.isUpdatingSplitAttributesSupported())
-
         val mockSplitAttributes = SplitAttributes()
         val mockSplitInfo = SplitInfo(
             ActivityStack(emptyList(), true, mock()),
@@ -106,9 +99,6 @@
 
     @Test
     fun test_invalidateTopVisibleSplitAttributes_delegates() {
-        whenever(mockBackend.areSplitAttributesUpdatesSupported()).thenReturn(true)
-        assertTrue(splitController.isInvalidatingTopVisibleSplitAttributesSupported())
-
         splitController.invalidateTopVisibleSplitAttributes()
         verify(mockBackend).invalidateTopVisibleSplitAttributes()
     }